From 49b41d162937750fbab947539db6c5bc0e1abe60 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 24 Dec 2017 09:41:00 -0800 Subject: [PATCH 001/131] GLES: Allow dual src blend on newer Intel. Fixes #10117. --- GPU/GLES/GPU_GLES.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index a1854df7a5..28bafda7cb 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -203,6 +203,26 @@ GPU_GLES::~GPU_GLES() { #endif } +static constexpr int MakeIntelSimpleVer(int v1, int v2, int v3) { + return (v1 << 16) | (v2 << 8) | v3; +} + +static bool HasIntelDualSrcBug(int versions[4]) { + // Intel uses a confusing set of at least 3 version numbering schemes. This is the one given to OpenGL. + switch (MakeIntelSimpleVer(versions[0], versions[1], versions[2])) { + case MakeIntelSimpleVer(9, 17, 10): + case MakeIntelSimpleVer(9, 18, 10): + return false; + case MakeIntelSimpleVer(10, 18, 10): + return versions[3] < 4061; + case MakeIntelSimpleVer(10, 18, 14): + return versions[3] < 4080; + default: + // Older than above didn't support dual src anyway, newer should have the fix. + return false; + } +} + // Take the raw GL extension and versioning data and turn into feature flags. void GPU_GLES::CheckGPUFeatures() { u32 features = 0; @@ -210,9 +230,20 @@ void GPU_GLES::CheckGPUFeatures() { features |= GPU_SUPPORTS_16BIT_FORMATS; if (gl_extensions.ARB_blend_func_extended || gl_extensions.EXT_blend_func_extended) { - if (gl_extensions.gpuVendor == GPU_VENDOR_INTEL || !gl_extensions.VersionGEThan(3, 0, 0)) { - // Don't use this extension to off on sub 3.0 OpenGL versions as it does not seem reliable - // Also on Intel, see https://github.com/hrydgard/ppsspp/issues/4867 + if (!gl_extensions.VersionGEThan(3, 0, 0)) { + // Don't use this extension on sub 3.0 OpenGL versions as it does not seem reliable + } else if (gl_extensions.gpuVendor == GPU_VENDOR_INTEL) { + // Also on Intel, see https://github.com/hrydgard/ppsspp/issues/10117 + // TODO: Remove entirely sometime reasonably far in driver years after 2015. + const std::string ver = draw_->GetInfoString(Draw::InfoField::APIVERSION); + int versions[4]{}; + if (sscanf(ver.c_str(), "Build %d.%d.%d.%d", &versions[0], &versions[1], &versions[2], &versions[3]) == 4) { + if (!HasIntelDualSrcBug(versions)) { + features |= GPU_SUPPORTS_DUALSOURCE_BLEND; + } + } else { + features |= GPU_SUPPORTS_DUALSOURCE_BLEND; + } } else { #ifdef __ANDROID__ // This appears to be broken on nVidia Shield TV. From cccf448ae0fcddb97090dfeb9c1dde499bb82d13 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 16:19:36 -0800 Subject: [PATCH 002/131] arm64jit: Allow disabling pointerification. For platforms where we can't get base aligned. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 60 ++++++++++++++++++++++++++++--- Core/MIPS/ARM64/Arm64RegCache.h | 2 ++ Core/MIPS/JitCommon/JitState.cpp | 6 ++++ Core/MIPS/JitCommon/JitState.h | 1 + 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 53d07ec05f..5754d3212b 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -150,6 +150,8 @@ bool Arm64RegCache::IsMappedAsPointer(MIPSGPReg mipsReg) { if (ar[mr[mipsReg].reg].pointerified) { ELOG("Really shouldn't be pointerified here"); } + } else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + return true; } return false; } @@ -315,6 +317,21 @@ ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { } return mr[mipsReg].reg; + } else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + // Was mapped as pointer, now we want it mapped as a value, presumably to + // add or subtract stuff to it. + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + ARM64Reg loadReg = armReg; + if (mipsReg == MIPS_REG_LO) { + loadReg = EncodeRegTo64(loadReg); + } + emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(mipsReg)); + } + mr[mipsReg].loc = ML_ARMREG; + if (mapFlags & MAP_DIRTY) { + ar[armReg].isDirty = true; + } + return (ARM64Reg)mr[mipsReg].reg; } // Okay, not mapped, so we need to allocate an ARM register. @@ -358,6 +375,11 @@ allocate: } Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) { + // Already mapped. + if (mr[reg].loc == ML_ARMREG_AS_PTR) { + return mr[reg].reg; + } + ARM64Reg retval = INVALID_REG; if (mr[reg].loc != ML_ARMREG && mr[reg].loc != ML_ARMREG_IMM) { retval = MapReg(reg); @@ -368,9 +390,23 @@ Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) { if (mr[reg].loc == ML_ARMREG || mr[reg].loc == ML_ARMREG_IMM) { // If there was an imm attached, discard it. mr[reg].loc = ML_ARMREG; - int a = DecodeReg(mr[reg].reg); - if (!ar[a].pointerified) { - emit_->MOVK(ARM64Reg(X0 + a), ((uint64_t)Memory::base) >> 32, SHIFT_32); + ARM64Reg a = DecodeReg(mr[reg].reg); + if (!jo_->enablePointerify) { + // First, flush the value. + if (ar[a].isDirty) { + ARM64Reg storeReg = ARM64RegForFlush(ar[a].mipsReg); + if (storeReg != INVALID_REG) + emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(ar[a].mipsReg)); + ar[a].isDirty = false; + } + + // Convert to a pointer by adding the base and clearing off the top bits. + // If SP, we can probably avoid the top bit clear, let's play with that later. + emit_->ANDI2R(a, a, 0x3FFFFFFF, INVALID_REG); + emit_->ADD(ARM64Reg(X0 + (int)a), ARM64Reg(X0 + (int)a), MEMBASEREG); + mr[reg].loc = ML_ARMREG_AS_PTR; + } else if (!ar[a].pointerified) { + emit_->MOVK(ARM64Reg(X0 + (int)a), ((uint64_t)Memory::base) >> 32, SHIFT_32); ar[a].pointerified = true; } } else { @@ -450,6 +486,7 @@ void Arm64RegCache::FlushArmReg(ARM64Reg r) { mreg.loc = ML_IMM; mreg.reg = INVALID_REG; } else { + _assert_msg_(JIT, mreg.loc != ML_ARMREG_AS_PTR, "Cannot flush reg as pointer"); // Note: may be a 64-bit reg. ARM64Reg storeReg = ARM64RegForFlush(ar[r].mipsReg); if (storeReg != INVALID_REG) @@ -476,7 +513,7 @@ void Arm64RegCache::DiscardR(MIPSGPReg mipsReg) { return; } const RegMIPSLoc prevLoc = mr[mipsReg].loc; - if (prevLoc == ML_ARMREG || prevLoc == ML_ARMREG_IMM) { + if (prevLoc == ML_ARMREG || prevLoc == ML_ARMREG_IMM || prevLoc == ML_ARMREG_AS_PTR) { ARM64Reg armReg = mr[mipsReg].reg; ar[armReg].isDirty = false; ar[armReg].mipsReg = MIPS_REG_INVALID; @@ -532,6 +569,9 @@ ARM64Reg Arm64RegCache::ARM64RegForFlush(MIPSGPReg r) { } return mr[r].reg; + case ML_ARMREG_AS_PTR: + return INVALID_REG; + case ML_MEM: return INVALID_REG; @@ -578,6 +618,14 @@ void Arm64RegCache::FlushR(MIPSGPReg r) { ar[mr[r].reg].pointerified = false; break; + case ML_ARMREG_AS_PTR: + // Never dirty. + if (ar[mr[r].reg].isDirty) { + ERROR_LOG_REPORT(JIT, "ARMREG_AS_PTR cannot be dirty (yet)"); + } + ar[mr[r].reg].mipsReg = MIPS_REG_INVALID; + break; + case ML_MEM: // Already there, nothing to do. break; @@ -792,7 +840,9 @@ ARM64Reg Arm64RegCache::R(MIPSGPReg mipsReg) { } ARM64Reg Arm64RegCache::RPtr(MIPSGPReg mipsReg) { - if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) { + if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + return (ARM64Reg)mr[mipsReg].reg; + } else if (mr[mipsReg].loc == ML_ARMREG || mr[mipsReg].loc == ML_ARMREG_IMM) { int a = mr[mipsReg].reg; if (ar[a].pointerified) { return (ARM64Reg)mr[mipsReg].reg; diff --git a/Core/MIPS/ARM64/Arm64RegCache.h b/Core/MIPS/ARM64/Arm64RegCache.h index d58a650688..e4bfc2e8cf 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.h +++ b/Core/MIPS/ARM64/Arm64RegCache.h @@ -41,6 +41,8 @@ enum { enum RegMIPSLoc { ML_IMM, ML_ARMREG, + // In an arm reg, but an adjusted pointer (not pointerified - unaligned.) + ML_ARMREG_AS_PTR, // In an arm reg, but also has a known immediate value. ML_ARMREG_IMM, ML_MEM, diff --git a/Core/MIPS/JitCommon/JitState.cpp b/Core/MIPS/JitCommon/JitState.cpp index 1d147d4c17..79a8daa8e7 100644 --- a/Core/MIPS/JitCommon/JitState.cpp +++ b/Core/MIPS/JitCommon/JitState.cpp @@ -51,8 +51,14 @@ namespace MIPSComp { continueMaxInstructions = 300; useStaticAlloc = false; + enablePointerify = false; #if PPSSPP_ARCH(ARM64) useStaticAlloc = true; + enablePointerify = true; +#endif +#if PPSSPP_PLATFORM(IOS) + useStaticAlloc = false; + enablePointerify = false; #endif } } diff --git a/Core/MIPS/JitCommon/JitState.h b/Core/MIPS/JitCommon/JitState.h index b1fe63892c..4e1d2c8898 100644 --- a/Core/MIPS/JitCommon/JitState.h +++ b/Core/MIPS/JitCommon/JitState.h @@ -198,6 +198,7 @@ namespace MIPSComp { // ARM64 only bool useASIMDVFPU; bool useStaticAlloc; + bool enablePointerify; // Common bool enableBlocklink; From 3fae092ecb3e67144e33c0e1c5cdd4b59f5774bc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 17:02:29 -0800 Subject: [PATCH 003/131] arm64jit: Only adjust pointers if pointerified. --- Core/MIPS/ARM64/Arm64CompALU.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/ARM64/Arm64CompALU.cpp b/Core/MIPS/ARM64/Arm64CompALU.cpp index 094686050e..beeeb7e6bb 100644 --- a/Core/MIPS/ARM64/Arm64CompALU.cpp +++ b/Core/MIPS/ARM64/Arm64CompALU.cpp @@ -86,7 +86,7 @@ void Arm64Jit::Comp_IType(MIPSOpcode op) { case 8: // same as addiu? case 9: // R(rt) = R(rs) + simm; break; //addiu // Special-case for small adjustments of pointerified registers. Commonly for SP but happens for others. - if (rs == rt && gpr.IsMappedAsPointer(rs) && IsImmArithmetic(simm < 0 ? -simm : simm, nullptr, nullptr)) { + if (rs == rt && jo.enablePointerify && gpr.IsMappedAsPointer(rs) && IsImmArithmetic(simm < 0 ? -simm : simm, nullptr, nullptr)) { ARM64Reg r32 = gpr.R(rs); gpr.MarkDirty(r32); ARM64Reg r = EncodeRegTo64(r32); From 9573a791b41297cf6f946f9725d5671fbb9dc4e6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 17:15:18 -0800 Subject: [PATCH 004/131] arm64jit: Skip storing spilled but not dirty. Unless IMM, we don't need to store non-dirty mapped regs. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 5754d3212b..8d375d03f0 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -486,11 +486,13 @@ void Arm64RegCache::FlushArmReg(ARM64Reg r) { mreg.loc = ML_IMM; mreg.reg = INVALID_REG; } else { - _assert_msg_(JIT, mreg.loc != ML_ARMREG_AS_PTR, "Cannot flush reg as pointer"); - // Note: may be a 64-bit reg. - ARM64Reg storeReg = ARM64RegForFlush(ar[r].mipsReg); - if (storeReg != INVALID_REG) - emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(ar[r].mipsReg)); + if (mreg.loc == ML_IMM || ar[r].isDirty) { + _assert_msg_(JIT, mreg.loc != ML_ARMREG_AS_PTR, "Cannot flush reg as pointer"); + // Note: may be a 64-bit reg. + ARM64Reg storeReg = ARM64RegForFlush(ar[r].mipsReg); + if (storeReg != INVALID_REG) + emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(ar[r].mipsReg)); + } mreg.loc = ML_MEM; mreg.reg = INVALID_REG; mreg.imm = 0; From d82efc4b0b43419507f640d8bf15b93a5c97e2c9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 17:29:18 -0800 Subject: [PATCH 005/131] arm64jit: Allow static alloc without pointerify. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 39 +++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 8d375d03f0..7493a4ad5a 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -57,7 +57,7 @@ void Arm64RegCache::Start(MIPSAnalyst::AnalysisResults &stats) { const StaticAllocation *statics = GetStaticAllocations(numStatics); for (int i = 0; i < numStatics; i++) { ar[statics[i].ar].mipsReg = statics[i].mr; - ar[statics[i].ar].pointerified = statics[i].pointerified; + ar[statics[i].ar].pointerified = statics[i].pointerified && jo_->enablePointerify; mr[statics[i].mr].loc = ML_ARMREG; mr[statics[i].mr].reg = statics[i].ar; mr[statics[i].mr].isStatic = true; @@ -110,7 +110,7 @@ void Arm64RegCache::EmitLoadStaticRegisters() { for (int i = 0; i < count; i++) { int offset = GetMipsRegOffset(allocs[i].mr); emit_->LDR(INDEX_UNSIGNED, allocs[i].ar, CTXREG, offset); - if (allocs[i].pointerified) { + if (allocs[i].pointerified && jo_->enablePointerify) { emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32); } } @@ -290,6 +290,17 @@ ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { mr[mipsReg].loc = ML_ARMREG_IMM; ar[armReg].pointerified = false; } + } else if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + // Was mapped as pointer, now we want it mapped as a value, presumably to + // add or subtract stuff to it. + if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { + ARM64Reg loadReg = armReg; + if (mipsReg == MIPS_REG_LO) { + loadReg = EncodeRegTo64(loadReg); + } + emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(mipsReg)); + } + mr[mipsReg].loc = ML_ARMREG; } // Erasing the imm on dirty (necessary since otherwise we will still think it's ML_ARMREG_IMM and return // true for IsImm and calculate crazily wrong things). /unknown @@ -504,14 +515,24 @@ void Arm64RegCache::FlushArmReg(ARM64Reg r) { void Arm64RegCache::DiscardR(MIPSGPReg mipsReg) { if (mr[mipsReg].isStatic) { - // Simply do nothing unless it's an IMM or ARMREG_IMM, in case we just switch it over to ARMREG, losing the value. + // Simply do nothing unless it's an IMM/ARMREG_IMM/ARMREG_AS_PTR, in case we just switch it over to ARMREG, losing the value. + ARM64Reg armReg = mr[mipsReg].reg; if (mr[mipsReg].loc == ML_ARMREG_IMM || mr[mipsReg].loc == ML_IMM) { - ARM64Reg armReg = mr[mipsReg].reg; // Ignore the imm value, restore sanity mr[mipsReg].loc = ML_ARMREG; ar[armReg].pointerified = false; ar[armReg].isDirty = false; } + if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { + ARM64Reg loadReg = armReg; + if (mipsReg == MIPS_REG_LO) { + loadReg = EncodeRegTo64(loadReg); + } + emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(mipsReg)); + mr[mipsReg].loc = ML_ARMREG; + ar[armReg].pointerified = false; + ar[armReg].isDirty = false; + } return; } const RegMIPSLoc prevLoc = mr[mipsReg].loc; @@ -704,6 +725,14 @@ void Arm64RegCache::FlushAll() { ar[armReg].pointerified = false; } mr[i].loc = ML_ARMREG; + } else if (mr[i].loc == ML_ARMREG_AS_PTR) { + // Need to reload the register (could also subtract, TODO...) + ARM64Reg loadReg = armReg; + if (mipsReg == MIPS_REG_LO) { + loadReg = EncodeRegTo64(loadReg); + } + emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(i)); + mr[i].loc = ML_ARMREG; } if (i != MIPS_REG_ZERO && mr[i].reg == INVALID_REG) { ELOG("ARM reg of static %i is invalid", i); @@ -717,7 +746,7 @@ void Arm64RegCache::FlushAll() { int count = 0; const StaticAllocation *allocs = GetStaticAllocations(count); for (int i = 0; i < count; i++) { - if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified) { + if (allocs[i].pointerified && !ar[allocs[i].ar].pointerified && jo_->enablePointerify) { // Re-pointerify emit_->MOVK(EncodeRegTo64(allocs[i].ar), ((uint64_t)Memory::base) >> 32, SHIFT_32); ar[allocs[i].ar].pointerified = true; From 7c2fc90def793a7c37af63368a11aa75491288cf Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 17:54:44 -0800 Subject: [PATCH 006/131] arm64jit: Avoid MOVK elsewhere without pointerify. --- Core/MIPS/ARM64/Arm64CompFPU.cpp | 12 ++++++++++-- Core/MIPS/ARM64/Arm64CompVFPU.cpp | 24 ++++++++++++++++++++---- Core/MIPS/ARM64/Arm64RegCache.cpp | 2 +- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index bab68ca302..f6cc5920f7 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -111,7 +111,11 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.LDR(32, INDEX_UNSIGNED, fpr.R(ft), SCRATCH1_64, 0); for (auto skip : skips) { @@ -139,7 +143,11 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.STR(32, INDEX_UNSIGNED, fpr.R(ft), SCRATCH1_64, 0); for (auto skip : skips) { diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 53105681a2..62f0cc9b5f 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -231,7 +231,11 @@ namespace MIPSComp { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } // Pointerify - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.LDR(32, INDEX_UNSIGNED, fpr.V(vt), SCRATCH1_64, 0); for (auto skip : skips) { @@ -261,7 +265,11 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.STR(32, INDEX_UNSIGNED, fpr.V(vt), SCRATCH1_64, 0); for (auto skip : skips) { @@ -303,7 +311,11 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, imm, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.LDP(32, INDEX_SIGNED, fpr.V(vregs[0]), fpr.V(vregs[1]), SCRATCH1_64, 0); @@ -332,7 +344,11 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, imm, SCRATCH2); } - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + if (jo.enablePointerify) { + MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); + } else { + ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); + } } fp.STP(32, INDEX_SIGNED, fpr.V(vregs[0]), fpr.V(vregs[1]), SCRATCH1_64, 0); diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 7493a4ad5a..b3f31d8a67 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -731,7 +731,7 @@ void Arm64RegCache::FlushAll() { if (mipsReg == MIPS_REG_LO) { loadReg = EncodeRegTo64(loadReg); } - emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(i)); + emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(MIPSGPReg(i))); mr[i].loc = ML_ARMREG; } if (i != MIPS_REG_ZERO && mr[i].reg == INVALID_REG) { From 4a27e99ee962f82f42127b683c6e9179ba50a97f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 19:30:10 -0800 Subject: [PATCH 007/131] arm64jit: Remove buggy icache invalidate. This is always of size 0, and crashes on iOS anyway... --- Common/Arm64Emitter.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index ecff815da1..165ab81b0b 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -3906,7 +3906,6 @@ void ARM64CodeBlock::PoisonMemory(int offset) { // AArch64: 0xD4200000 = BRK 0 while (ptr < maxptr) *ptr++ = 0xD4200000; - FlushIcacheSection((u8 *)ptr, (u8 *)maxptr); } } // namespace From 257a4fdd127791805e02f7db60b3672526c8e94c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 19:33:02 -0800 Subject: [PATCH 008/131] arm64jit: Reprotect fixed code after icache flush. --- Common/CodeBlock.h | 4 ++++ Core/MIPS/ARM64/Arm64Jit.cpp | 1 + 2 files changed, 5 insertions(+) diff --git a/Common/CodeBlock.h b/Common/CodeBlock.h index 03ae3f8606..a0f5dd55f4 100644 --- a/Common/CodeBlock.h +++ b/Common/CodeBlock.h @@ -68,6 +68,10 @@ public: // If not WX Exclusive, no need to call ProtectMemoryPages because we never change the protection from RWX. PoisonMemory(offset); ResetCodePtr(offset); + if (PlatformIsWXExclusive()) { + // Need to re-protect the part we didn't clear. + ProtectMemoryPages(region, offset, MEM_PROT_READ | MEM_PROT_EXEC); + } } // BeginWrite/EndWrite assume that we keep appending. diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index ae9b6b8f4e..d656c0f951 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -129,6 +129,7 @@ void Arm64Jit::ClearCache() { ILOG("ARM64Jit: Clearing the cache!"); blocks.Clear(); ClearCodeSpace(jitStartOffset); + FlushIcacheSection(region + jitStartOffset, region + region_size - jitStartOffset); } void Arm64Jit::InvalidateCacheAt(u32 em_address, int length) { From 092f98d313386219e95040432fe91470070b578c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 27 Dec 2017 19:34:43 -0800 Subject: [PATCH 009/131] arm64jit: Fix an integer truncation warning. --- Core/MIPS/ARM64/Arm64CompFPU.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index f6cc5920f7..c9194b8ebe 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -357,7 +357,8 @@ void Arm64Jit::Comp_mxc1(MIPSOpcode op) case 4: //FI(fs) = R(rt); break; //mtc1 if (gpr.IsImm(rt)) { - uint32_t ival = gpr.GetImm(rt); + // This can't be run on LO/HI. + uint32_t ival = (uint32_t)gpr.GetImm(rt); float floatval; memcpy(&floatval, &ival, sizeof(floatval)); uint8_t imm8; From 6fd17fb026b8529f52e24ed9a7b1e48f1bdc1959 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 09:35:33 -0800 Subject: [PATCH 010/131] arm64jit: Use reg sum for LDR/STR. Skips an add, and should be less ops anyway. --- Common/Arm64Emitter.cpp | 2 +- Core/MIPS/ARM64/Arm64CompFPU.cpp | 18 ++++------------- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 2 +- Core/MIPS/ARM64/Arm64CompVFPU.cpp | 27 ++++++++------------------ 4 files changed, 14 insertions(+), 35 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 165ab81b0b..7bf2eac22a 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -2073,7 +2073,7 @@ void ARM64FloatEmitter::EmitLoadStoreImmediate(u8 size, u32 opc, IndexType type, } else { - _assert_msg_(DYNA_REC, !(imm < -256 || imm > 255), "%s immediate offset must be within range of -256 to 256!", __FUNCTION__); + _assert_msg_(DYNA_REC, !(imm < -256 || imm > 255), "%s immediate offset must be within range of -256 to 255!", __FUNCTION__); encoded_imm = (imm & 0x1FF) << 2; if (type == INDEX_POST) encoded_imm |= 1; diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index c9194b8ebe..8398bbd5f6 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -103,7 +103,7 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) fpr.MapReg(ft, MAP_NOINIT | MAP_DIRTY); if (gpr.IsImm(rs)) { u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; - gpr.SetRegImm(SCRATCH1_64, (uintptr_t)(Memory::base + addr)); + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -111,13 +111,8 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - if (jo.enablePointerify) { - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); - } else { - ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); - } } - fp.LDR(32, INDEX_UNSIGNED, fpr.R(ft), SCRATCH1_64, 0); + fp.LDR(32, fpr.R(ft), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } @@ -135,7 +130,7 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) fpr.MapReg(ft); if (gpr.IsImm(rs)) { u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; - gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)(Memory::base)); + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -143,13 +138,8 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - if (jo.enablePointerify) { - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); - } else { - ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); - } } - fp.STR(32, INDEX_UNSIGNED, fpr.R(ft), SCRATCH1_64, 0); + fp.STR(32, fpr.R(ft), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 8ec1dbc75a..af2452e1b0 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -135,7 +135,7 @@ namespace MIPSComp { std::vector skips; if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) { - u32 addr = iaddr; + u32 addr = iaddr & 0x3FFFFFFF; // Need to initialize since this only loads part of the register. // But rs no longer matters (even if rs == rt) since we have the address. gpr.MapReg(rt, load ? MAP_DIRTY : 0); diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 62f0cc9b5f..ab729c71db 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -221,8 +221,8 @@ namespace MIPSComp { // CC might be set by slow path below, so load regs first. fpr.MapRegV(vt, MAP_DIRTY | MAP_NOINIT); if (gpr.IsImm(rs)) { - u32 addr = offset + gpr.GetImm(rs); - gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); + u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -230,14 +230,8 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - // Pointerify - if (jo.enablePointerify) { - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); - } else { - ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); - } } - fp.LDR(32, INDEX_UNSIGNED, fpr.V(vt), SCRATCH1_64, 0); + fp.LDR(32, fpr.V(vt), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } @@ -256,8 +250,8 @@ namespace MIPSComp { // CC might be set by slow path below, so load regs first. fpr.MapRegV(vt); if (gpr.IsImm(rs)) { - u32 addr = offset + gpr.GetImm(rs); - gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); + u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); if (g_Config.bFastMemory) { @@ -265,13 +259,8 @@ namespace MIPSComp { } else { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } - if (jo.enablePointerify) { - MOVK(SCRATCH1_64, ((uint64_t)Memory::base) >> 32, SHIFT_32); - } else { - ADD(SCRATCH1_64, SCRATCH1_64, MEMBASEREG); - } } - fp.STR(32, INDEX_UNSIGNED, fpr.V(vt), SCRATCH1_64, 0); + fp.STR(32, fpr.V(vt), SCRATCH1_64, ArithOption(MEMBASEREG)); for (auto skip : skips) { SetJumpTarget(skip); } @@ -302,7 +291,7 @@ namespace MIPSComp { fpr.MapRegsAndSpillLockV(vregs, V_Quad, MAP_DIRTY | MAP_NOINIT); if (gpr.IsImm(rs)) { - u32 addr = imm + gpr.GetImm(rs); + u32 addr = (imm + gpr.GetImm(rs)) & 0x3FFFFFFF; gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); } else { gpr.MapReg(rs); @@ -335,7 +324,7 @@ namespace MIPSComp { fpr.MapRegsAndSpillLockV(vregs, V_Quad, 0); if (gpr.IsImm(rs)) { - u32 addr = imm + gpr.GetImm(rs); + u32 addr = (imm + gpr.GetImm(rs)) & 0x3FFFFFFF; gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); } else { gpr.MapReg(rs); From c4c28282cfca3e5024e6193c128f2ec81341eadb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 10:40:31 -0800 Subject: [PATCH 011/131] arm64jit: Allow ARMREG_AS_PTR to be dirty. Since we can just add/sub, then it should be valid (as long as we only offset it when dirty.) --- Core/MIPS/ARM64/Arm64RegCache.cpp | 53 ++++++++++++------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index b3f31d8a67..0ff30a91df 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -213,6 +213,7 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) { mr[mipsReg].loc = ML_ARMREG_IMM; break; default: + _assert_msg_(JIT, mr[mipsReg].loc != ML_ARMREG_AS_PTR, "MapRegTo with a pointer?"); mr[mipsReg].loc = ML_ARMREG; break; } @@ -294,11 +295,7 @@ ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { // Was mapped as pointer, now we want it mapped as a value, presumably to // add or subtract stuff to it. if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { - ARM64Reg loadReg = armReg; - if (mipsReg == MIPS_REG_LO) { - loadReg = EncodeRegTo64(loadReg); - } - emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(mipsReg)); + emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG); } mr[mipsReg].loc = ML_ARMREG; } @@ -332,11 +329,7 @@ ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { // Was mapped as pointer, now we want it mapped as a value, presumably to // add or subtract stuff to it. if ((mapFlags & MAP_NOINIT) != MAP_NOINIT) { - ARM64Reg loadReg = armReg; - if (mipsReg == MIPS_REG_LO) { - loadReg = EncodeRegTo64(loadReg); - } - emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(mipsReg)); + emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG); } mr[mipsReg].loc = ML_ARMREG; if (mapFlags & MAP_DIRTY) { @@ -413,11 +406,10 @@ Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) { // Convert to a pointer by adding the base and clearing off the top bits. // If SP, we can probably avoid the top bit clear, let's play with that later. - emit_->ANDI2R(a, a, 0x3FFFFFFF, INVALID_REG); - emit_->ADD(ARM64Reg(X0 + (int)a), ARM64Reg(X0 + (int)a), MEMBASEREG); + emit_->ADD(EncodeRegTo64(a), EncodeRegTo64(a), MEMBASEREG); mr[reg].loc = ML_ARMREG_AS_PTR; } else if (!ar[a].pointerified) { - emit_->MOVK(ARM64Reg(X0 + (int)a), ((uint64_t)Memory::base) >> 32, SHIFT_32); + emit_->MOVK(EncodeRegTo64(a), ((uint64_t)Memory::base) >> 32, SHIFT_32); ar[a].pointerified = true; } } else { @@ -498,7 +490,11 @@ void Arm64RegCache::FlushArmReg(ARM64Reg r) { mreg.reg = INVALID_REG; } else { if (mreg.loc == ML_IMM || ar[r].isDirty) { - _assert_msg_(JIT, mreg.loc != ML_ARMREG_AS_PTR, "Cannot flush reg as pointer"); + if (mreg.loc == ML_ARMREG_AS_PTR) { + // Unpointerify, in case dirty. + emit_->SUB(EncodeRegTo64(r), EncodeRegTo64(r), MEMBASEREG); + mreg.loc = ML_ARMREG; + } // Note: may be a 64-bit reg. ARM64Reg storeReg = ARM64RegForFlush(ar[r].mipsReg); if (storeReg != INVALID_REG) @@ -517,22 +513,12 @@ void Arm64RegCache::DiscardR(MIPSGPReg mipsReg) { if (mr[mipsReg].isStatic) { // Simply do nothing unless it's an IMM/ARMREG_IMM/ARMREG_AS_PTR, in case we just switch it over to ARMREG, losing the value. ARM64Reg armReg = mr[mipsReg].reg; - if (mr[mipsReg].loc == ML_ARMREG_IMM || mr[mipsReg].loc == ML_IMM) { + if (mr[mipsReg].loc == ML_ARMREG_IMM || mr[mipsReg].loc == ML_IMM || mr[mipsReg].loc == ML_ARMREG_AS_PTR) { // Ignore the imm value, restore sanity mr[mipsReg].loc = ML_ARMREG; ar[armReg].pointerified = false; ar[armReg].isDirty = false; } - if (mr[mipsReg].loc == ML_ARMREG_AS_PTR) { - ARM64Reg loadReg = armReg; - if (mipsReg == MIPS_REG_LO) { - loadReg = EncodeRegTo64(loadReg); - } - emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(mipsReg)); - mr[mipsReg].loc = ML_ARMREG; - ar[armReg].pointerified = false; - ar[armReg].isDirty = false; - } return; } const RegMIPSLoc prevLoc = mr[mipsReg].loc; @@ -642,9 +628,15 @@ void Arm64RegCache::FlushR(MIPSGPReg r) { break; case ML_ARMREG_AS_PTR: - // Never dirty. if (ar[mr[r].reg].isDirty) { - ERROR_LOG_REPORT(JIT, "ARMREG_AS_PTR cannot be dirty (yet)"); + emit_->SUB(EncodeRegTo64(mr[r].reg), EncodeRegTo64(mr[r].reg), MEMBASEREG); + // We set this so ARM64RegForFlush knows it's no longer a pointer. + mr[r].loc = ML_ARMREG; + ARM64Reg storeReg = ARM64RegForFlush(r); + if (storeReg != INVALID_REG) { + emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(r)); + } + ar[mr[r].reg].isDirty = false; } ar[mr[r].reg].mipsReg = MIPS_REG_INVALID; break; @@ -726,12 +718,7 @@ void Arm64RegCache::FlushAll() { } mr[i].loc = ML_ARMREG; } else if (mr[i].loc == ML_ARMREG_AS_PTR) { - // Need to reload the register (could also subtract, TODO...) - ARM64Reg loadReg = armReg; - if (mipsReg == MIPS_REG_LO) { - loadReg = EncodeRegTo64(loadReg); - } - emit_->LDR(INDEX_UNSIGNED, loadReg, CTXREG, GetMipsRegOffset(MIPSGPReg(i))); + emit_->SUB(EncodeRegTo64(armReg), EncodeRegTo64(armReg), MEMBASEREG); mr[i].loc = ML_ARMREG; } if (i != MIPS_REG_ZERO && mr[i].reg == INVALID_REG) { From 2e1d85a55b5e87e2d42479d2a799db928357b494 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 10:45:50 -0800 Subject: [PATCH 012/131] arm64jit: Allow reg ptr offsets when unaligned. Since now they support being dirty. --- Core/MIPS/ARM64/Arm64CompALU.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompALU.cpp b/Core/MIPS/ARM64/Arm64CompALU.cpp index beeeb7e6bb..dfa1d8b0d6 100644 --- a/Core/MIPS/ARM64/Arm64CompALU.cpp +++ b/Core/MIPS/ARM64/Arm64CompALU.cpp @@ -86,8 +86,8 @@ void Arm64Jit::Comp_IType(MIPSOpcode op) { case 8: // same as addiu? case 9: // R(rt) = R(rs) + simm; break; //addiu // Special-case for small adjustments of pointerified registers. Commonly for SP but happens for others. - if (rs == rt && jo.enablePointerify && gpr.IsMappedAsPointer(rs) && IsImmArithmetic(simm < 0 ? -simm : simm, nullptr, nullptr)) { - ARM64Reg r32 = gpr.R(rs); + if (rs == rt && gpr.IsMappedAsPointer(rs) && IsImmArithmetic(simm < 0 ? -simm : simm, nullptr, nullptr)) { + ARM64Reg r32 = gpr.RPtr(rs); gpr.MarkDirty(r32); ARM64Reg r = EncodeRegTo64(r32); if (simm > 0) { From 08e85d0cd4cf52cce66368862b13f85246a357a5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 10:48:55 -0800 Subject: [PATCH 013/131] arm64jit: Autodetect pointerify support. Also, re-enable static alloc without pointerify, it works now. --- Core/MIPS/ARM64/Arm64Jit.cpp | 5 +++++ Core/MIPS/JitCommon/JitState.cpp | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index d656c0f951..86b80a3a57 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -66,6 +66,11 @@ using namespace Arm64Gen; using namespace Arm64JitConstants; Arm64Jit::Arm64Jit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), fpr(mips, &js, &jo), mips_(mips), fp(this) { + // Automatically disable incompatible options. + if (((intptr_t)Memory::base & 0x00000000FFFFFFFFUL) != 0) { + jo.enablePointerify = false; + } + logBlocks = 0; dontLogBlocks = 0; blocks.Init(); diff --git a/Core/MIPS/JitCommon/JitState.cpp b/Core/MIPS/JitCommon/JitState.cpp index 79a8daa8e7..7a603daf50 100644 --- a/Core/MIPS/JitCommon/JitState.cpp +++ b/Core/MIPS/JitCommon/JitState.cpp @@ -54,11 +54,8 @@ namespace MIPSComp { enablePointerify = false; #if PPSSPP_ARCH(ARM64) useStaticAlloc = true; + // iOS/etc. may disable at runtime if Memory::base is not nicely aligned. enablePointerify = true; -#endif -#if PPSSPP_PLATFORM(IOS) - useStaticAlloc = false; - enablePointerify = false; #endif } } From 8c3b0aa89ca27d987b88a931da022703b98306ed Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 11:12:06 -0800 Subject: [PATCH 014/131] jit: Skip unlinking blocks on clear. Speeds up clear especially when on a WX exclusive platform. --- Core/MIPS/JitCommon/JitBlockCache.cpp | 17 ++++++++++------- Core/MIPS/JitCommon/JitBlockCache.h | 9 ++++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Core/MIPS/JitCommon/JitBlockCache.cpp b/Core/MIPS/JitCommon/JitBlockCache.cpp index ad6466e8d1..c7f6e5a346 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.cpp +++ b/Core/MIPS/JitCommon/JitBlockCache.cpp @@ -105,7 +105,7 @@ void JitBlockCache::Clear() { block_map_.clear(); proxyBlockMap_.clear(); for (int i = 0; i < num_blocks_; i++) - DestroyBlock(i, false); + DestroyBlock(i, DestroyType::CLEAR); links_to_.clear(); num_blocks_ = 0; @@ -474,7 +474,7 @@ void JitBlockCache::RestoreSavedEmuHackOps(std::vector saved) { } } -void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { +void JitBlockCache::DestroyBlock(int block_num, DestroyType type) { if (block_num < 0 || block_num >= num_blocks_) { ERROR_LOG_REPORT(JIT, "DestroyBlock: Invalid block number %d", block_num); return; @@ -493,7 +493,7 @@ void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { int proxied_blocknum = GetBlockNumberFromStartAddress((*b->proxyFor)[i], false); // If it was already cleared, we don't know which to destroy. if (proxied_blocknum != -1) { - DestroyBlock(proxied_blocknum, invalidate); + DestroyBlock(proxied_blocknum, type); } } b->proxyFor->clear(); @@ -513,7 +513,7 @@ void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { // In this case we probably "leak" the proxy block currently (no memory leak but it'll stay enabled). if (b->invalid) { - if (invalidate) + if (type == DestroyType::INVALIDATE) ERROR_LOG(JIT, "Invalidating invalid block %d", block_num); return; } @@ -535,7 +535,10 @@ void JitBlockCache::DestroyBlock(int block_num, bool invalidate) { } if (b->checkedEntry) { - MIPSComp::jit->UnlinkBlock(b->checkedEntry, b->originalAddress); + // We can skip this if we're clearing anyway, which cuts down on protect back and forth on WX exclusive. + if (type != DestroyType::CLEAR) { + MIPSComp::jit->UnlinkBlock(b->checkedEntry, b->originalAddress); + } } else { ERROR_LOG(JIT, "Unlinking block with no entry: %08x (%d)", b->originalAddress, block_num); } @@ -567,7 +570,7 @@ void JitBlockCache::InvalidateICache(u32 address, const u32 length) { const u32 blockStart = next->first.second; const u32 blockEnd = next->first.first; if (blockStart < pEnd && blockEnd > pAddr) { - DestroyBlock(next->second, true); + DestroyBlock(next->second, DestroyType::INVALIDATE); // Our iterator is now invalid. Break and search again. // Most of the time there shouldn't be a bunch of matching blocks. goto restart; @@ -587,7 +590,7 @@ void JitBlockCache::InvalidateChangedBlocks() { const u32 emuhack = GetEmuHackOpForBlock(block_num).encoding; if (Memory::ReadUnchecked_U32(b.originalAddress) != emuhack) { DEBUG_LOG(JIT, "Invalidating changed block at %08x", b.originalAddress); - DestroyBlock(block_num, true); + DestroyBlock(block_num, DestroyType::INVALIDATE); } } } diff --git a/Core/MIPS/JitCommon/JitBlockCache.h b/Core/MIPS/JitCommon/JitBlockCache.h index 7041f31284..6e01b80ad1 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.h +++ b/Core/MIPS/JitCommon/JitBlockCache.h @@ -43,6 +43,13 @@ struct BlockCacheStats { std::map bloatMap; }; +enum class DestroyType { + DESTROY, + INVALIDATE, + // Skips jit unlink, since it'll be poisoned anyway. + CLEAR, +}; + // Define this in order to get VTune profile support for the Jit generated code. // Add the VTune include/lib directories to the project directories to get this to build. // #define USE_VTUNE @@ -126,7 +133,7 @@ public: // DOES NOT WORK CORRECTLY WITH JIT INLINING void InvalidateICache(u32 address, const u32 length); void InvalidateChangedBlocks(); - void DestroyBlock(int block_num, bool invalidate); + void DestroyBlock(int block_num, DestroyType type); // No jit operations may be run between these calls. // Meant to be used to make memory safe for savestates, memcpy, etc. From 48fe0168f17583aa533fa7007f42f06cd1594c2c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 11:21:45 -0800 Subject: [PATCH 015/131] x86jit: Fix safemem on WX exclusive. --- Core/MIPS/x86/JitSafeMem.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/MIPS/x86/JitSafeMem.cpp b/Core/MIPS/x86/JitSafeMem.cpp index 339f5e0ec1..56374ed828 100644 --- a/Core/MIPS/x86/JitSafeMem.cpp +++ b/Core/MIPS/x86/JitSafeMem.cpp @@ -440,6 +440,7 @@ void JitSafeMemFuncs::Init(ThunkManager *thunks) { AllocCodeSpace(FUNCS_ARENA_SIZE); thunks_ = thunks; + BeginWrite(); readU32 = GetCodePtr(); CreateReadFunc(32, (const void *)&Memory::Read_U32); readU16 = GetCodePtr(); @@ -453,6 +454,7 @@ void JitSafeMemFuncs::Init(ThunkManager *thunks) { CreateWriteFunc(16, (const void *)&Memory::Write_U16); writeU8 = GetCodePtr(); CreateWriteFunc(8, (const void *)&Memory::Write_U8); + EndWrite(); } void JitSafeMemFuncs::Shutdown() { From 1b792c32e115c60b46aa30fb3a12943f45be32e1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 12:28:45 -0800 Subject: [PATCH 016/131] arm64jit: Attempt to reuse imms on sw/etc. Mostly, this handles the zero case, but it may help in other cases too. --- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 84 +++++++++++++++----------- Core/MIPS/ARM64/Arm64RegCache.cpp | 23 +++++++ Core/MIPS/ARM64/Arm64RegCache.h | 2 + 3 files changed, 75 insertions(+), 34 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index af2452e1b0..63fbe9d44b 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -294,7 +294,8 @@ namespace MIPSComp { u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF; std::vector skips; - ARM64Reg addrReg = SCRATCH1; + ARM64Reg targetReg = INVALID_REG; + ARM64Reg addrReg = INVALID_REG; int dataSize = 4; switch (o) { @@ -335,51 +336,66 @@ namespace MIPSComp { gpr.SpillLock(rs, rt); gpr.MapRegAsPointer(rs); - Arm64Gen::ARM64Reg ar; - if (!load && gpr.IsImm(rt) && gpr.GetImm(rt) == 0) { - // Can just store from the zero register directly. - ar = WZR; - } else { + // For a store, try to avoid mapping a reg if not needed. + targetReg = load ? INVALID_REG : gpr.MapTempImm(rt); + if (targetReg == INVALID_REG) { gpr.MapReg(rt, load ? MAP_NOINIT : 0); - ar = gpr.R(rt); + targetReg = gpr.R(rt); } switch (o) { - case 35: LDR(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 37: LDRH(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 33: LDRSH(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 36: LDRB(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 32: LDRSB(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; + case 35: LDR(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 37: LDRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 33: LDRSH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 36: LDRB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 32: LDRSB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; // Store - case 43: STR(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 41: STRH(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; - case 40: STRB(INDEX_UNSIGNED, ar, gpr.RPtr(rs), offset); break; + case 43: STR(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 41: STRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; + case 40: STRB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; } gpr.ReleaseSpillLocks(); break; } } + if (!load && gpr.IsImm(rt) && gpr.MapTempImm(rt) != INVALID_REG) { + // We're storing an immediate value, let's see if we can optimize rt. + if (!gpr.IsImm(rs) || offset == 0) { + // In this case, we're always going to need rs mapped, which may flush the temp imm. + // We handle that in the cases below since targetReg is INVALID_REG. + gpr.MapIn(rs); + } + + targetReg = gpr.MapTempImm(rt); + } + if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) { - // TODO: Avoid mapping a register for the "zero" register, use R0 instead. - - // We can compute the full address at compile time. Kickass. - u32 addr = iaddr & 0x3FFFFFFF; - - if (addr == iaddr && offset == 0) { + if (offset == 0) { // It was already safe. Let's shove it into a reg and use it directly. - load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + if (targetReg == INVALID_REG) { + load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + targetReg = gpr.R(rt); + } addrReg = gpr.R(rs); } else { - // In this case, only map rt. rs+offset will be in R0. - gpr.MapReg(rt, load ? MAP_NOINIT : 0); - gpr.SetRegImm(SCRATCH1, addr); + // In this case, only map rt. rs+offset will be in SCRATCH1. + if (targetReg == INVALID_REG) { + gpr.MapReg(rt, load ? MAP_NOINIT : 0); + targetReg = gpr.R(rt); + } + gpr.SetRegImm(SCRATCH1, iaddr); addrReg = SCRATCH1; } } else { // This actually gets hit in micro machines! rs = ZR rt = ZR. Probably a bug. // Leaving this a debug assert for future investigation. _dbg_assert_msg_(JIT, !gpr.IsImm(rs), "Invalid immediate address? CPU bug?"); - load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + + // If we already have a targetReg, we optimized an imm, and rs is already mapped. + if (targetReg == INVALID_REG) { + load ? gpr.MapDirtyIn(rt, rs) : gpr.MapInIn(rt, rs); + targetReg = gpr.R(rt); + } if (!g_Config.bFastMemory && rs != MIPS_REG_SP) { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); @@ -391,15 +407,15 @@ namespace MIPSComp { switch (o) { // Load - case 35: LDR(gpr.R(rt), MEMBASEREG, addrReg); break; - case 37: LDRH(gpr.R(rt), MEMBASEREG, addrReg); break; - case 33: LDRSH(gpr.R(rt), MEMBASEREG, addrReg); break; - case 36: LDRB(gpr.R(rt), MEMBASEREG, addrReg); break; - case 32: LDRSB(gpr.R(rt), MEMBASEREG, addrReg); break; + case 35: LDR(targetReg, MEMBASEREG, addrReg); break; + case 37: LDRH(targetReg, MEMBASEREG, addrReg); break; + case 33: LDRSH(targetReg, MEMBASEREG, addrReg); break; + case 36: LDRB(targetReg, MEMBASEREG, addrReg); break; + case 32: LDRSB(targetReg, MEMBASEREG, addrReg); break; // Store - case 43: STR(gpr.R(rt), MEMBASEREG, addrReg); break; - case 41: STRH(gpr.R(rt), MEMBASEREG, addrReg); break; - case 40: STRB(gpr.R(rt), MEMBASEREG, addrReg); break; + case 43: STR(targetReg, MEMBASEREG, addrReg); break; + case 41: STRH(targetReg, MEMBASEREG, addrReg); break; + case 40: STRB(targetReg, MEMBASEREG, addrReg); break; } for (auto skip : skips) { SetJumpTarget(skip); diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 0ff30a91df..4bcb05e485 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -264,6 +264,29 @@ ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { return INVALID_REG; } +ARM64Reg Arm64RegCache::MapTempImm(MIPSGPReg r) { + // If already mapped, no need for a temporary. + if (IsMapped(r)) { + return R(r); + } + + if (mr[r].loc == ML_IMM) { + if (mr[r].imm == 0) { + return WZR; + } + + // Try our luck - check for an exact match in another armreg. + for (int i = 0; i < NUM_MIPSREG; ++i) { + if (mr[i].loc == ML_ARMREG_IMM && mr[i].imm == mr[r].imm) { + // Awesome, let's just use this reg. + return mr[i].reg; + } + } + } + + return INVALID_REG; +} + // TODO: Somewhat smarter spilling - currently simply spills the first available, should do // round robin or FIFO or something. ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { diff --git a/Core/MIPS/ARM64/Arm64RegCache.h b/Core/MIPS/ARM64/Arm64RegCache.h index e4bfc2e8cf..2773915a35 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.h +++ b/Core/MIPS/ARM64/Arm64RegCache.h @@ -101,6 +101,8 @@ public: // Optimally set a register to an imm value (possibly using another register.) void SetRegImm(Arm64Gen::ARM64Reg reg, u64 imm); + Arm64Gen::ARM64Reg MapTempImm(MIPSGPReg); + // Returns an ARM register containing the requested MIPS register. Arm64Gen::ARM64Reg MapReg(MIPSGPReg reg, int mapFlags = 0); Arm64Gen::ARM64Reg MapRegAsPointer(MIPSGPReg reg); From b21af3321a8e28cd5763dd31407da8662cba85ee Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 14:36:37 -0800 Subject: [PATCH 017/131] arm64jit: Allow ANDI2R/etc. with zero. Since they're all possible anyway, and it's easy to forget to special case 0. --- Common/Arm64Emitter.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 7bf2eac22a..1c49846536 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -3669,8 +3669,10 @@ void ARM64XEmitter::ANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) imm &= 0xFFFFFFFF; if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { AND(Rd, Rn, imm_r, imm_s, n != 0); + } else if (imm == 0) { + MOVI2R(Rd, 0); } else { - _assert_msg_(JIT, scratch != INVALID_REG, "ANDSI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); + _assert_msg_(JIT, scratch != INVALID_REG, "ANDI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); AND(Rd, Rn, scratch); } @@ -3680,6 +3682,10 @@ void ARM64XEmitter::ORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) unsigned int n, imm_s, imm_r; if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { ORR(Rd, Rn, imm_r, imm_s, n != 0); + } else if (imm == 0) { + if (Rd != Rn) { + MOV(Rd, Rn); + } } else { _assert_msg_(JIT, scratch != INVALID_REG, "ORRI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); @@ -3691,6 +3697,10 @@ void ARM64XEmitter::EORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) unsigned int n, imm_s, imm_r; if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { EOR(Rd, Rn, imm_r, imm_s, n != 0); + } else if (imm == 0) { + if (Rd != Rn) { + MOV(Rd, Rn); + } } else { _assert_msg_(JIT, scratch != INVALID_REG, "EORI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); @@ -3702,6 +3712,8 @@ void ARM64XEmitter::ANDSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) unsigned int n, imm_s, imm_r; if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { ANDS(Rd, Rn, imm_r, imm_s, n != 0); + } else if (imm == 0) { + ANDS(Rd, Rn, Is64Bit(Rn) ? ZR : WZR, ArithOption(Rd, ST_LSL, 0)); } else { _assert_msg_(JIT, scratch != INVALID_REG, "ANDSI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); @@ -3783,6 +3795,9 @@ bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { AND(Rd, Rn, imm_r, imm_s, n != 0); return true; + } else if (imm == 0) { + MOVI2R(Rd, 0); + return true; } else { return false; } @@ -3792,6 +3807,11 @@ bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { ORR(Rd, Rn, imm_r, imm_s, n != 0); return true; + } else if (imm == 0) { + if (Rd != Rn) { + MOV(Rd, Rn); + } + return true; } else { return false; } @@ -3801,6 +3821,11 @@ bool ARM64XEmitter::TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { EOR(Rd, Rn, imm_r, imm_s, n != 0); return true; + } else if (imm == 0) { + if (Rd != Rn) { + MOV(Rd, Rn); + } + return true; } else { return false; } @@ -3892,7 +3917,7 @@ void ARM64XEmitter::SUBSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) if (IsImmArithmetic(imm, &val, &shift)) { SUBS(Rd, Rn, val, shift); } else { - _assert_msg_(JIT, scratch != INVALID_REG, "ANDSI2R - failed to construct immediate value from %08x, need scratch", (u32)imm); + _assert_msg_(JIT, scratch != INVALID_REG, "SUBSI2R - failed to construct immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); SUBS(Rd, Rn, scratch); } From 970326c9e5040e9b01d9c2cdd8bd90f8c83b65a4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 14:49:55 -0800 Subject: [PATCH 018/131] arm64jit: Fix and enable imm lwl/lwr. --- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 63fbe9d44b..f76b703087 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -129,8 +129,6 @@ namespace MIPSComp { } } - DISABLE; - u32 iaddr = gpr.IsImm(rs) ? offset + gpr.GetImm(rs) : 0xFFFFFFFF; std::vector skips; @@ -159,35 +157,21 @@ namespace MIPSComp { case 42: // swl LDR(SCRATCH2, MEMBASEREG, SCRATCH1); ANDI2R(SCRATCH2, SCRATCH2, 0xffffff00 << shift, INVALID_REG); - ORR(SCRATCH2, SCRATCH2, SCRATCH2, ArithOption(gpr.R(rt), ST_LSR, 24 - shift)); + ORR(SCRATCH2, SCRATCH2, gpr.R(rt), ArithOption(gpr.R(rt), ST_LSR, 24 - shift)); STR(SCRATCH2, MEMBASEREG, SCRATCH1); break; case 46: // swr LDR(SCRATCH2, MEMBASEREG, SCRATCH1); ANDI2R(SCRATCH2, SCRATCH2, 0x00ffffff >> (24 - shift), INVALID_REG); - ORR(SCRATCH2, SCRATCH2, SCRATCH2, ArithOption(gpr.R(rt), ST_LSL, shift)); + ORR(SCRATCH2, SCRATCH2, gpr.R(rt), ArithOption(gpr.R(rt), ST_LSL, shift)); STR(SCRATCH2, MEMBASEREG, SCRATCH1); break; } return; } - switch (o) { - case 34: // lwl - DISABLE; - break; - - case 38: // lwr - DISABLE; - break; - - case 42: // swl - break; - - case 46: // swr - break; - } + DISABLE; _dbg_assert_msg_(JIT, !gpr.IsImm(rs), "Invalid immediate address? CPU bug?"); if (load) { From 1b1e2c773beb76d01a71c7d1c6de54ff174a1d27 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 15:54:03 -0800 Subject: [PATCH 019/131] arm64jit: Jit lwl/lwr with proper temp regs. It's possible rt might overlap with w9/w10, so we really need to allocate these properly. This locks and spills as necessary. --- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 35 ++++------ Core/MIPS/ARM64/Arm64RegCache.cpp | 88 ++++++++++++++++---------- Core/MIPS/ARM64/Arm64RegCache.h | 4 ++ 3 files changed, 70 insertions(+), 57 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index f76b703087..a6cc9cf9eb 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -171,8 +171,6 @@ namespace MIPSComp { return; } - DISABLE; - _dbg_assert_msg_(JIT, !gpr.IsImm(rs), "Invalid immediate address? CPU bug?"); if (load) { gpr.MapDirtyIn(rt, rs, false); @@ -186,14 +184,10 @@ namespace MIPSComp { SetScratch1ToEffectiveAddress(rs, offset); } - // Need temp regs. TODO: Get from the regcache? - static const ARM64Reg LR_SCRATCH3 = W9; - static const ARM64Reg LR_SCRATCH4 = W10; - if (false && load) { - PUSH(EncodeRegTo64(LR_SCRATCH3)); - } else { - PUSH2(EncodeRegTo64(LR_SCRATCH3), EncodeRegTo64(LR_SCRATCH4)); - } + // We can lose rs now, since we have it in SCRATCH1. + gpr.SpillLock(rt); + ARM64Reg LR_SCRATCH3 = gpr.GetAndLockTempR(); + ARM64Reg LR_SCRATCH4 = gpr.GetAndLockTempR(); // Here's our shift amount. ANDI2R(SCRATCH2, SCRATCH1, 3); @@ -205,7 +199,7 @@ namespace MIPSComp { switch (o) { case 34: // lwl MOVI2R(LR_SCRATCH3, 0x00ffffff); - LDR(SCRATCH1, MEMBASEREG, SCRATCH1); + LDR(SCRATCH1, MEMBASEREG, ArithOption(SCRATCH1)); LSRV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2); AND(gpr.R(rt), gpr.R(rt), LR_SCRATCH3); NEG(SCRATCH2, SCRATCH2); @@ -216,7 +210,7 @@ namespace MIPSComp { case 38: // lwr MOVI2R(LR_SCRATCH3, 0xffffff00); - LDR(SCRATCH1, MEMBASEREG, SCRATCH1); + LDR(SCRATCH1, MEMBASEREG, ArithOption(SCRATCH1)); LSRV(SCRATCH1, SCRATCH1, SCRATCH2); NEG(SCRATCH2, SCRATCH2); ADDI2R(SCRATCH2, SCRATCH2, 24); @@ -227,19 +221,20 @@ namespace MIPSComp { case 42: // swl MOVI2R(LR_SCRATCH3, 0xffffff00); - LDR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + LDR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); LSLV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2); AND(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3); NEG(SCRATCH2, SCRATCH2); ADDI2R(SCRATCH2, SCRATCH2, 24); + LSRV(LR_SCRATCH3, gpr.R(rt), SCRATCH2); ORR(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3); - STR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + STR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); break; case 46: // swr MOVI2R(LR_SCRATCH3, 0x00ffffff); - LDR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + LDR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); NEG(SCRATCH2, SCRATCH2); ADDI2R(SCRATCH2, SCRATCH2, 24); LSRV(LR_SCRATCH3, LR_SCRATCH3, SCRATCH2); @@ -248,19 +243,15 @@ namespace MIPSComp { ADDI2R(SCRATCH2, SCRATCH2, 24); LSLV(LR_SCRATCH3, gpr.R(rt), SCRATCH2); ORR(LR_SCRATCH4, LR_SCRATCH4, LR_SCRATCH3); - STR(LR_SCRATCH4, MEMBASEREG, SCRATCH1); + STR(LR_SCRATCH4, MEMBASEREG, ArithOption(SCRATCH1)); break; } - if (false && load) { - POP(EncodeRegTo64(LR_SCRATCH3)); - } else { - POP2(EncodeRegTo64(LR_SCRATCH3), EncodeRegTo64(LR_SCRATCH4)); - } - for (auto skip : skips) { SetJumpTarget(skip); } + + gpr.ReleaseSpillLocks(); } void Arm64Jit::Comp_ITypeMem(MIPSOpcode op) { diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 4bcb05e485..97a65d7eeb 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -226,6 +226,43 @@ void Arm64RegCache::MapRegTo(ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags) { mr[mipsReg].reg = reg; } +ARM64Reg Arm64RegCache::AllocateReg() { + int allocCount; + const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount); + +allocate: + for (int i = 0; i < allocCount; i++) { + ARM64Reg reg = allocOrder[i]; + + if (ar[reg].mipsReg == MIPS_REG_INVALID && !ar[reg].tempLocked) { + return reg; + } + } + + // Still nothing. Let's spill a reg and goto 10. + // TODO: Use age or something to choose which register to spill? + // TODO: Spill dirty regs first? or opposite? + bool clobbered; + ARM64Reg bestToSpill = FindBestToSpill(true, &clobbered); + if (bestToSpill == INVALID_REG) { + bestToSpill = FindBestToSpill(false, &clobbered); + } + + if (bestToSpill != INVALID_REG) { + if (clobbered) { + DiscardR(ar[bestToSpill].mipsReg); + } else { + FlushArmReg(bestToSpill); + } + // Now one must be free. + goto allocate; + } + + // Uh oh, we have all of them spilllocked.... + ERROR_LOG_REPORT(JIT, "Out of spillable registers at PC %08x!!!", mips_->pc); + return INVALID_REG; +} + ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { int allocCount; const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount); @@ -287,6 +324,14 @@ ARM64Reg Arm64RegCache::MapTempImm(MIPSGPReg r) { return INVALID_REG; } +ARM64Reg Arm64RegCache::GetAndLockTempR() { + ARM64Reg reg = AllocateReg(); + if (reg != INVALID_REG) { + ar[reg].tempLocked = true; + } + return reg; +} + // TODO: Somewhat smarter spilling - currently simply spills the first available, should do // round robin or FIFO or something. ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { @@ -362,43 +407,13 @@ ARM64Reg Arm64RegCache::MapReg(MIPSGPReg mipsReg, int mapFlags) { } // Okay, not mapped, so we need to allocate an ARM register. - - int allocCount; - const ARM64Reg *allocOrder = GetMIPSAllocationOrder(allocCount); - -allocate: - for (int i = 0; i < allocCount; i++) { - ARM64Reg reg = allocOrder[i]; - - if (ar[reg].mipsReg == MIPS_REG_INVALID) { - // That means it's free. Grab it, and load the value into it (if requested). - MapRegTo(reg, mipsReg, mapFlags); - return reg; - } + ARM64Reg reg = AllocateReg(); + if (reg != INVALID_REG) { + // Grab it, and load the value into it (if requested). + MapRegTo(reg, mipsReg, mapFlags); } - // Still nothing. Let's spill a reg and goto 10. - // TODO: Use age or something to choose which register to spill? - // TODO: Spill dirty regs first? or opposite? - bool clobbered; - ARM64Reg bestToSpill = FindBestToSpill(true, &clobbered); - if (bestToSpill == INVALID_REG) { - bestToSpill = FindBestToSpill(false, &clobbered); - } - - if (bestToSpill != INVALID_REG) { - if (clobbered) { - DiscardR(ar[bestToSpill].mipsReg); - } else { - FlushArmReg(bestToSpill); - } - // Now one must be free. - goto allocate; - } - - // Uh oh, we have all of them spilllocked.... - ERROR_LOG_REPORT(JIT, "Out of spillable registers at PC %08x!!!", mips_->pc); - return INVALID_REG; + return reg; } Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) { @@ -864,6 +879,9 @@ void Arm64RegCache::ReleaseSpillLocks() { if (!mr[i].isStatic) mr[i].spillLock = false; } + for (int i = 0; i < NUM_ARMREG; i++) { + ar[i].tempLocked = false; + } } void Arm64RegCache::ReleaseSpillLock(MIPSGPReg reg) { diff --git a/Core/MIPS/ARM64/Arm64RegCache.h b/Core/MIPS/ARM64/Arm64RegCache.h index 2773915a35..b81f4bbb0c 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.h +++ b/Core/MIPS/ARM64/Arm64RegCache.h @@ -62,6 +62,7 @@ struct RegARM64 { MIPSGPReg mipsReg; // if -1, no mipsreg attached. bool isDirty; // Should the register be written back? bool pointerified; // Has used movk to move the memory base into the top part of the reg. Note - still usable as 32-bit reg! + bool tempLocked; // Reserved for a temp register. }; struct RegMIPS { @@ -124,6 +125,8 @@ public: void FlushR(MIPSGPReg r); void DiscardR(MIPSGPReg r); + Arm64Gen::ARM64Reg GetAndLockTempR(); + Arm64Gen::ARM64Reg R(MIPSGPReg preg); // Returns a cached register, while checking that it's NOT mapped as a pointer Arm64Gen::ARM64Reg RPtr(MIPSGPReg preg); // Returns a cached register, if it has been mapped as a pointer @@ -147,6 +150,7 @@ private: const StaticAllocation *GetStaticAllocations(int &count); const Arm64Gen::ARM64Reg *GetMIPSAllocationOrder(int &count); void MapRegTo(Arm64Gen::ARM64Reg reg, MIPSGPReg mipsReg, int mapFlags); + Arm64Gen::ARM64Reg AllocateReg(); Arm64Gen::ARM64Reg FindBestToSpill(bool unusedOnly, bool *clobbered); Arm64Gen::ARM64Reg ARM64RegForFlush(MIPSGPReg r); From 27116dcb86ce1f4dfc7caa6eaf036ad995f4eeb9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 16:04:34 -0800 Subject: [PATCH 020/131] arm64jit: Avoid flushing when mapping as pointer. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 97a65d7eeb..3dc61aadeb 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -434,14 +434,6 @@ Arm64Gen::ARM64Reg Arm64RegCache::MapRegAsPointer(MIPSGPReg reg) { mr[reg].loc = ML_ARMREG; ARM64Reg a = DecodeReg(mr[reg].reg); if (!jo_->enablePointerify) { - // First, flush the value. - if (ar[a].isDirty) { - ARM64Reg storeReg = ARM64RegForFlush(ar[a].mipsReg); - if (storeReg != INVALID_REG) - emit_->STR(INDEX_UNSIGNED, storeReg, CTXREG, GetMipsRegOffset(ar[a].mipsReg)); - ar[a].isDirty = false; - } - // Convert to a pointer by adding the base and clearing off the top bits. // If SP, we can probably avoid the top bit clear, let's play with that later. emit_->ADD(EncodeRegTo64(a), EncodeRegTo64(a), MEMBASEREG); From 0151c5e649167b4fe6e7950134ef6953a12f6704 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 19:27:45 -0800 Subject: [PATCH 021/131] GPU: Track stats on uploads. A lot can lead to slowdown, so it's useful when logging frame drops. --- GPU/Common/FramebufferCommon.cpp | 1 + GPU/D3D11/GPU_D3D11.cpp | 3 ++- GPU/Directx9/GPU_DX9.cpp | 3 ++- GPU/GLES/GPU_GLES.cpp | 3 ++- GPU/GPU.h | 2 ++ GPU/Vulkan/GPU_Vulkan.cpp | 3 ++- 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index c8516d4984..9e0081ec8e 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -738,6 +738,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int DrawTextureFlags flags = (vfb || g_Config.iBufFilter == SCALE_LINEAR) ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; Bind2DShader(); DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); + gpuStats.numUploads++; } void FramebufferManagerCommon::CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags) { diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index df3a9aa6f1..24dcfdd734 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -527,7 +527,7 @@ void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" - "Readbacks: %d\n" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment shaders loaded: %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -546,6 +546,7 @@ void GPU_D3D11::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerD3D11_->GetNumVertexShaders(), shaderManagerD3D11_->GetNumFragmentShaders() ); diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index fb071c7a50..158442285c 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -501,7 +501,7 @@ void GPU_DX9::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" - "Readbacks: %d\n" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment shaders loaded: %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -520,6 +520,7 @@ void GPU_DX9::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerDX9_->GetNumVertexShaders(), shaderManagerDX9_->GetNumFragmentShaders() ); diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 9f94d37aa6..96b7763c25 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -733,7 +733,7 @@ void GPU_GLES::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" - "Readbacks: %d\n" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment, Programs loaded: %i, %i, %i\n", gpuStats.msProcessingDisplayLists * 1000.0f, gpuStats.numDrawCalls, @@ -752,6 +752,7 @@ void GPU_GLES::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerGL_->GetNumVertexShaders(), shaderManagerGL_->GetNumFragmentShaders(), shaderManagerGL_->GetNumPrograms()); diff --git a/GPU/GPU.h b/GPU/GPU.h index 7e10db49aa..08d036560c 100644 --- a/GPU/GPU.h +++ b/GPU/GPU.h @@ -68,6 +68,7 @@ struct GPUStatistics { numFlushes = 0; numTexturesDecoded = 0; numReadbacks = 0; + numUploads = 0; numClears = 0; msProcessingDisplayLists = 0; vertexGPUCycles = 0; @@ -88,6 +89,7 @@ struct GPUStatistics { int numShaderSwitches; int numTexturesDecoded; int numReadbacks; + int numUploads; int numClears; double msProcessingDisplayLists; int vertexGPUCycles; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 2f3689081a..b7db5cd197 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -668,7 +668,7 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { "Cached, Uncached Vertices Drawn: %i, %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i invalidated: %i\n" - "Readbacks: %d\n" + "Readbacks: %d, uploads: %d\n" "Vertex, Fragment, Pipelines loaded: %i, %i, %i\n" "Pushbuffer space used: UBO %d, Vtx %d, Idx %d\n" "%s\n", @@ -689,6 +689,7 @@ void GPU_Vulkan::GetStats(char *buffer, size_t bufsize) { gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numReadbacks, + gpuStats.numUploads, shaderManagerVulkan_->GetNumVertexShaders(), shaderManagerVulkan_->GetNumFragmentShaders(), pipelineManager_->GetNumPipelines(), From 28da05fa7a33ca75d7241b8cdcdcc9745be476a2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 19:28:39 -0800 Subject: [PATCH 022/131] HLE: Replace starocean framebuf clear func. This reduces the performance impact significantly, by skipping the memset uploads for each line. Fixes #10466. --- Core/HLE/ReplaceTables.cpp | 86 ++++++++++++++++++++++++++++++-------- Core/MIPS/MIPSAnalyst.cpp | 1 + 2 files changed, 70 insertions(+), 17 deletions(-) diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 5e1891a301..8dd52b2dcd 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -41,6 +41,14 @@ #include #endif +enum class GPUReplacementSkip { + MEMSET = 1, + MEMCPY = 2, + MEMMOVE = 4, +}; + +static int skipGPUReplacements = 0; + // I think these have to be pretty accurate as these are libc replacements, // but we can probably get away with approximating the VFPU vsin/vcos and vrot // pretty roughly. @@ -119,8 +127,10 @@ static int Replace_memcpy() { // Some games use memcpy on executable code. We need to flush emuhack ops. currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -159,8 +169,10 @@ static int Replace_memcpy_jak() { return 5; } currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -197,8 +209,10 @@ static int Replace_memcpy16() { // Some games use memcpy on executable code. We need to flush emuhack ops. currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -220,8 +234,10 @@ static int Replace_memcpy_swizzled() { u32 srcPtr = PARAM(1); u32 pitch = PARAM(2); u32 h = PARAM(4); - if (Memory::IsVRAMAddress(srcPtr)) { - gpu->PerformMemoryDownload(srcPtr, pitch * h); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMCPY) == 0) { + if (Memory::IsVRAMAddress(srcPtr)) { + gpu->PerformMemoryDownload(srcPtr, pitch * h); + } } u8 *dstp = Memory::GetPointer(destPtr); const u8 *srcp = Memory::GetPointer(srcPtr); @@ -258,9 +274,11 @@ static int Replace_memmove() { bool skip = false; // Some games use memcpy on executable code. We need to flush emuhack ops. - currentMIPS->InvalidateICache(srcPtr, bytes); - if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { - skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + if ((skipGPUReplacements & (int)GPUReplacementSkip::MEMMOVE) == 0) { + currentMIPS->InvalidateICache(srcPtr, bytes); + if (Memory::IsVRAMAddress(destPtr) || Memory::IsVRAMAddress(srcPtr)) { + skip = gpu->PerformMemoryCopy(destPtr, srcPtr, bytes); + } } if (!skip && bytes != 0) { u8 *dst = Memory::GetPointer(destPtr); @@ -282,7 +300,7 @@ static int Replace_memset() { u8 value = PARAM(1); u32 bytes = PARAM(2); bool skip = false; - if (Memory::IsVRAMAddress(destPtr)) { + if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) { skip = gpu->PerformMemorySet(destPtr, value, bytes); } if (!skip && bytes != 0) { @@ -309,7 +327,7 @@ static int Replace_memset_jak() { } bool skip = false; - if (Memory::IsVRAMAddress(destPtr)) { + if (Memory::IsVRAMAddress(destPtr) && (skipGPUReplacements & (int)GPUReplacementSkip::MEMSET) == 0) { skip = gpu->PerformMemorySet(destPtr, value, bytes); } if (!skip && bytes != 0) { @@ -599,6 +617,17 @@ static bool GetMIPSStaticAddress(u32 &addr, s32 lui_offset, s32 lw_offset) { return true; } +static bool GetMIPSGPAddress(u32 &addr, s32 offset) { + const MIPSOpcode loadOp = Memory::Read_Instruction(currentMIPS->pc + offset, true); + if (MIPS_GET_RS(loadOp) == MIPS_REG_GP) { + s16 gpoff = (s16)(u16)(loadOp & 0x0000FFFF); + addr = currentMIPS->r[MIPS_REG_GP] + gpoff; + return true; + } + + return false; +} + static int Hook_godseaterburst_blit_texture() { u32 texaddr; // Only if there's no texture. @@ -1032,7 +1061,6 @@ static int Hook_tonyhawkp8_upload_tutorial_frame() { const u32 fb_address = currentMIPS->r[MIPS_REG_A0]; if (Memory::IsVRAMAddress(fb_address)) { gpu->PerformMemoryUpload(fb_address, 0x00088000); - CBreakPoints::ExecMemCheck(fb_address, true, 0x00088000, currentMIPS->pc); } return 0; } @@ -1145,7 +1173,6 @@ static int Hook_mytranwars_upload_frame() { u32 fb_address = currentMIPS->r[MIPS_REG_S0]; if (Memory::IsVRAMAddress(fb_address)) { gpu->PerformMemoryUpload(fb_address, 0x00088000); - CBreakPoints::ExecMemCheck(fb_address, true, 0x00088000, currentMIPS->pc); } return 0; } @@ -1183,6 +1210,29 @@ static int Hook_marvelalliance1_copy_after() { return 0; } +static int Hook_starocean_clear_framebuf_before() { + skipGPUReplacements |= (int)GPUReplacementSkip::MEMSET; + return 0; +} + +static int Hook_starocean_clear_framebuf_after() { + skipGPUReplacements &= ~(int)GPUReplacementSkip::MEMSET; + + // This hook runs after the copy, this is the final memcpy destination. + u32 framebuf = currentMIPS->r[MIPS_REG_V0] - 512 * 4 * 271; + u32 y_address, h_address; + + if (GetMIPSGPAddress(y_address, -204) && GetMIPSGPAddress(h_address, -200)) { + int y = (s16)Memory::Read_U16(y_address); + int h = (s16)Memory::Read_U16(h_address); + + DEBUG_LOG(HLE, "starocean_clear_framebuf() - %08x y=%d-%d", framebuf, y, h); + // TODO: This is always clearing to 0, actually, which could be faster than an upload. + gpu->PerformMemoryUpload(framebuf + 512 * y * 4, 512 * h * 4); + } + return 0; +} + #define JITFUNC(f) (&MIPSComp::MIPSFrontendInterface::f) // Can either replace with C functions or functions emitted in Asm/ArmAsm. @@ -1292,6 +1342,8 @@ static const ReplacementTableEntry entries[] = { { "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x638 }, { "marvelalliance1_copy", &Hook_marvelalliance1_copy_a1_before, 0, REPFLAG_HOOKENTER, 0x664 }, { "marvelalliance1_copy", &Hook_marvelalliance1_copy_after, 0, REPFLAG_HOOKENTER, 0x69c }, + { "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_before, 0, REPFLAG_HOOKENTER, 0 }, + { "starocean_clear_framebuf", &Hook_starocean_clear_framebuf_after, 0, REPFLAG_HOOKEXIT, 0 }, {} }; @@ -1306,6 +1358,8 @@ void Replacement_Init() { continue; replacementNameLookup[entry->name].push_back(i); } + + skipGPUReplacements = 0; } void Replacement_Shutdown() { @@ -1313,8 +1367,6 @@ void Replacement_Shutdown() { replacementNameLookup.clear(); } -// TODO: Do something on load state? - int GetNumReplacementFuncs() { return ARRAY_SIZE(entries); } diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index d651ec7827..3070c864b3 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -382,6 +382,7 @@ static const HardHashTableEntry hardcodedHashes[] = { { 0xb0ef265e87899f0a, 32, "vector_divide_t_s", }, { 0xb183a37baa12607b, 32, "vscl_t", }, { 0xb1a3e60a89af9857, 20, "fabs", }, + { 0xb25670ff47b4843d, 232, "starocean_clear_framebuf" }, { 0xb3fef47fb27d57c9, 44, "vector_scale_t", }, { 0xb43fd5078ae78029, 84, "send_commandi_stall", }, { 0xb43ffbd4dc446dd2, 324, "atan2f", }, From 5177db0f9188ca4540d215f317401d6e8c1b7d03 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 28 Dec 2017 23:58:30 -0800 Subject: [PATCH 023/131] arm64jit: Remove unnecessary address masking. We use views like on x86_64, so this isn't needed. --- Core/MIPS/ARM64/Arm64CompFPU.cpp | 4 ++-- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 5 ++--- Core/MIPS/ARM64/Arm64CompVFPU.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index 8398bbd5f6..ea971de24f 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -102,7 +102,7 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) fpr.SpillLock(ft); fpr.MapReg(ft, MAP_NOINIT | MAP_DIRTY); if (gpr.IsImm(rs)) { - u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + u32 addr = offset + gpr.GetImm(rs); gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); @@ -129,7 +129,7 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) fpr.MapReg(ft); if (gpr.IsImm(rs)) { - u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + u32 addr = offset + gpr.GetImm(rs); gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index a6cc9cf9eb..5219c1a239 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -133,13 +133,12 @@ namespace MIPSComp { std::vector skips; if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) { - u32 addr = iaddr & 0x3FFFFFFF; // Need to initialize since this only loads part of the register. // But rs no longer matters (even if rs == rt) since we have the address. gpr.MapReg(rt, load ? MAP_DIRTY : 0); - gpr.SetRegImm(SCRATCH1, addr & ~3); + gpr.SetRegImm(SCRATCH1, iaddr & ~3); - u8 shift = (addr & 3) * 8; + u8 shift = (iaddr & 3) * 8; switch (o) { case 34: // lwl diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index ab729c71db..0277df28e7 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -221,7 +221,7 @@ namespace MIPSComp { // CC might be set by slow path below, so load regs first. fpr.MapRegV(vt, MAP_DIRTY | MAP_NOINIT); if (gpr.IsImm(rs)) { - u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + u32 addr = offset + gpr.GetImm(rs); gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); @@ -250,7 +250,7 @@ namespace MIPSComp { // CC might be set by slow path below, so load regs first. fpr.MapRegV(vt); if (gpr.IsImm(rs)) { - u32 addr = (offset + gpr.GetImm(rs)) & 0x3FFFFFFF; + u32 addr = offset + gpr.GetImm(rs); gpr.SetRegImm(SCRATCH1, addr); } else { gpr.MapReg(rs); @@ -291,7 +291,7 @@ namespace MIPSComp { fpr.MapRegsAndSpillLockV(vregs, V_Quad, MAP_DIRTY | MAP_NOINIT); if (gpr.IsImm(rs)) { - u32 addr = (imm + gpr.GetImm(rs)) & 0x3FFFFFFF; + u32 addr = imm + gpr.GetImm(rs); gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); } else { gpr.MapReg(rs); @@ -324,7 +324,7 @@ namespace MIPSComp { fpr.MapRegsAndSpillLockV(vregs, V_Quad, 0); if (gpr.IsImm(rs)) { - u32 addr = (imm + gpr.GetImm(rs)) & 0x3FFFFFFF; + u32 addr = imm + gpr.GetImm(rs); gpr.SetRegImm(SCRATCH1_64, addr + (uintptr_t)Memory::base); } else { gpr.MapReg(rs); From d00dcb4400fd24cb3e14f0bb963204d685b4b2d8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 08:55:49 -0800 Subject: [PATCH 024/131] UI: Use atomic flags in game info cache. Simpler, no need to lock the entire info when checking these. --- UI/GameInfoCache.cpp | 36 ++++++++++++++---------------------- UI/GameInfoCache.h | 7 ++----- UI/GameScreen.cpp | 6 +++--- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index ad5ddd7bd2..aed2220376 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -46,6 +46,7 @@ GameInfoCache *g_gameInfoCache; GameInfo::GameInfo() : fileType(IdentifiedFileType::UNKNOWN) { + pending = true; } GameInfo::~GameInfo() { @@ -300,16 +301,6 @@ std::string GameInfo::GetTitle() { return title; } -bool GameInfo::IsPending() { - std::lock_guard guard(lock); - return pending; -} - -bool GameInfo::IsWorking() { - std::lock_guard guard(lock); - return working; -} - void GameInfo::SetTitle(const std::string &newTitle) { std::lock_guard guard(lock); title = newTitle; @@ -368,12 +359,8 @@ public: if (!info_->LoadFromPath(gamePath_)) return; - { - std::lock_guard lock(info_->lock); - info_->working = true; - info_->fileType = Identify_File(info_->GetFileLoader().get()); - } - + info_->working = true; + info_->fileType = Identify_File(info_->GetFileLoader().get()); switch (info_->fileType) { case IdentifiedFileType::PSP_PBP: case IdentifiedFileType::PSP_PBP_DIRECTORY: @@ -392,6 +379,7 @@ public: goto handleELF; } ERROR_LOG(LOADER, "invalid pbp %s\n", pbpLoader->Path().c_str()); + info_->working = false; return; } @@ -556,11 +544,15 @@ handleELF: // TODO: This will currently read in the whole directory tree. Not really necessary for just a // few files. auto fl = info_->GetFileLoader(); - if (!fl) + if (!fl) { + info_->working = false; return; // Happens with UWP currently, TODO... + } BlockDevice *bd = constructBlockDevice(info_->GetFileLoader().get()); - if (!bd) + if (!bd) { + info_->working = false; return; // nothing to do here.. + } ISOFileSystem umd(&handles, bd); // Alright, let's fetch the PARAM.SFO. @@ -639,7 +631,6 @@ handleELF: info_->installDataSize = info_->GetInstallDataSizeInBytes(); } - std::lock_guard lock(info_->lock); info_->pending = false; info_->working = false; // ILOG("Completed writing info for %s", info_->GetTitle().c_str()); @@ -721,7 +712,8 @@ void GameInfoCache::PurgeType(IdentifiedFileType fileType) { gameInfoWQ_->Flush(); restart: for (auto iter = info_.begin(); iter != info_.end(); iter++) { - if (iter->second->fileType == fileType) { + auto &info = iter->second; + if (!info->working && info->fileType == fileType) { info_.erase(iter); goto restart; } @@ -729,7 +721,7 @@ void GameInfoCache::PurgeType(IdentifiedFileType fileType) { } void GameInfoCache::WaitUntilDone(std::shared_ptr &info) { - while (info->IsPending()) { + while (info->pending) { if (gameInfoWQ_->WaitUntilDone(false)) { // A true return means everything finished, so bail out. // This way even if something gets stuck, we won't hang. @@ -769,7 +761,7 @@ std::shared_ptr GameInfoCache::GetInfo(Draw::DrawContext *draw, const info = std::make_shared(); } - if (info->IsWorking()) { + if (info->working) { // Uh oh, it's currently in process. It could mark pending = false with the wrong wantFlags. // Let's wait it out, then queue. // NOTE: This is bad because we're likely on the UI thread.... diff --git a/UI/GameInfoCache.h b/UI/GameInfoCache.h index 0b4201d4a4..ccad4ba683 100644 --- a/UI/GameInfoCache.h +++ b/UI/GameInfoCache.h @@ -108,9 +108,6 @@ public: std::string GetTitle(); void SetTitle(const std::string &newTitle); - bool IsPending(); - bool IsWorking(); - // Hold this when reading or writing from the GameInfo. // Don't need to hold it when just passing around the pointer, // and obviously also not when creating it and holding the only pointer @@ -142,8 +139,8 @@ public: u64 gameSize = 0; u64 saveDataSize = 0; u64 installDataSize = 0; - bool pending = true; - bool working = false; + std::atomic pending{}; + std::atomic working{}; protected: // Note: this can change while loading, use GetTitle(). diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 05ae58a517..740ef7467d 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -123,7 +123,7 @@ void GameScreen::CreateViews() { rightColumnItems->Add(btnDeleteSaveData_)->OnClick.Handle(this, &GameScreen::OnDeleteSaveData); btnDeleteSaveData_->SetVisibility(V_GONE); - if (info && !info->IsPending()) { + if (info && !info->pending) { otherChoices_.clear(); } @@ -252,7 +252,7 @@ void GameScreen::update() { btnSetBackground_->SetVisibility(UI::V_VISIBLE); } } - if (!info->IsPending()) { + if (!info->pending) { // At this point, the above buttons won't become visible. We can show these now. for (UI::Choice *choice : otherChoices_) { choice->SetVisibility(UI::V_VISIBLE); @@ -419,7 +419,7 @@ void SetBackgroundPopupScreen::update() { PopupScreen::update(); std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, GAMEINFO_WANTBG | GAMEINFO_WANTBGDATA); - if (status_ == Status::PENDING && info && !info->IsPending()) { + if (status_ == Status::PENDING && info && !info->pending) { GameInfoTex *pic = nullptr; if (info->pic1.dataLoaded && info->pic1.data.size()) { pic = &info->pic1; From d03d2f0b2b99e1b7f6e717cd4dc828d65e3feaf0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 08:56:27 -0800 Subject: [PATCH 025/131] UI: Check existance outside of game info lock. Improves behavior when remote files timeout, at least they won't block FlushBGs(). --- UI/GameInfoCache.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index aed2220376..83b97f2b72 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -228,7 +228,7 @@ bool GameInfo::LoadFromPath(const std::string &gamePath) { title = File::GetFilename(filePath_); } - return fileLoader ? fileLoader->Exists() : true; + return true; } std::shared_ptr GameInfo::GetFileLoader() { @@ -358,6 +358,11 @@ public: void run() override { if (!info_->LoadFromPath(gamePath_)) return; + // In case of a remote file, check if it actually exists before locking. + if (!info_->GetFileLoader()->Exists()) { + info_->pending = false; + return; + } info_->working = true; info_->fileType = Identify_File(info_->GetFileLoader().get()); From 2498ce5e3e4c12c6dd47cd266ceaffc0da0ee875 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 13:52:52 -0800 Subject: [PATCH 026/131] arm64jit: Oops, properly init temp locked flag. Fixes #10469. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 3dc61aadeb..ca1e8fd03d 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -45,6 +45,7 @@ void Arm64RegCache::Start(MIPSAnalyst::AnalysisResults &stats) { ar[i].mipsReg = MIPS_REG_INVALID; ar[i].isDirty = false; ar[i].pointerified = false; + ar[i].tempLocked = false; } for (int i = 0; i < NUM_MIPSREG; i++) { mr[i].loc = ML_MEM; From 2dda2bfa78d28e24f1a371d7e940a1301a0b655a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 30 Dec 2017 00:09:06 +0100 Subject: [PATCH 027/131] Dual source blend: Remove bad #ifdef that killed dual src code on Android... oops. Re-enable on nVidia Shield TV/Tablet. Fixes #10136 properly. --- GPU/Common/GPUStateUtils.cpp | 2 -- GPU/GLES/GPU_GLES.cpp | 7 ------- GPU/Vulkan/GPU_Vulkan.cpp | 8 ++------ 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 9386758515..6060449807 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -784,12 +784,10 @@ static const BlendEq eqLookup[] = { static BlendFactor toDualSource(BlendFactor blendfunc) { switch (blendfunc) { -#if !defined(USING_GLES2) // TODO: Remove when we have better headers case BlendFactor::SRC_ALPHA: return BlendFactor::SRC1_ALPHA; case BlendFactor::ONE_MINUS_SRC_ALPHA: return BlendFactor::ONE_MINUS_SRC1_ALPHA; -#endif default: return blendfunc; } diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 96b7763c25..b6f112a0ae 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -245,14 +245,7 @@ void GPU_GLES::CheckGPUFeatures() { features |= GPU_SUPPORTS_DUALSOURCE_BLEND; } } else { -#ifdef __ANDROID__ - // This appears to be broken on nVidia Shield TV. - if (gl_extensions.gpuVendor != GPU_VENDOR_NVIDIA) { - features |= GPU_SUPPORTS_DUALSOURCE_BLEND; - } -#else features |= GPU_SUPPORTS_DUALSOURCE_BLEND; -#endif } } diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index b7db5cd197..5bdd2fc086 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -214,12 +214,8 @@ void GPU_Vulkan::CheckGPUFeatures() { } if (vulkan_->GetFeaturesEnabled().dualSrcBlend) { switch (vulkan_->GetPhysicalDeviceProperties().vendorID) { - case VULKAN_VENDOR_NVIDIA: - // Workaround for Shield TV and Shield Tablet driver bug. - if (strcmp(vulkan_->GetPhysicalDeviceProperties().deviceName, "NVIDIA Tegra X1") != 0 && - strcmp(vulkan_->GetPhysicalDeviceProperties().deviceName, "NVIDIA Tegra K1") != 0) - features |= GPU_SUPPORTS_DUALSOURCE_BLEND; - break; + // We thought we had a bug here on nVidia but turns out we accidentally #ifdef-ed out crucial + // code on Android. case VULKAN_VENDOR_INTEL: // Workaround for Intel driver bug. break; From 1ecce2a2e11d65c622d753f601c72fa34d1f1ce1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 11:40:26 -0800 Subject: [PATCH 028/131] arm64jit: Reuse code in I2R funcs. --- Common/Arm64Emitter.cpp | 72 +++++++++++--------------------- Common/Arm64Emitter.h | 12 +++--- Core/MIPS/ARM64/Arm64CompALU.cpp | 6 +-- Core/MIPS/ARM64/Arm64Jit.h | 4 +- 4 files changed, 36 insertions(+), 58 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 1c49846536..ad834e1872 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -3664,14 +3664,10 @@ void ARM64FloatEmitter::ABI_PopRegisters(uint32_t registers, uint32_t fp_registe } void ARM64XEmitter::ANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - unsigned int n, imm_s, imm_r; + // It's probably okay to AND by extra bits. if (!Is64Bit(Rn)) imm &= 0xFFFFFFFF; - if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { - AND(Rd, Rn, imm_r, imm_s, n != 0); - } else if (imm == 0) { - MOVI2R(Rd, 0); - } else { + if (!TryANDI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "ANDI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); AND(Rd, Rn, scratch); @@ -3679,14 +3675,8 @@ void ARM64XEmitter::ANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::ORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - unsigned int n, imm_s, imm_r; - if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { - ORR(Rd, Rn, imm_r, imm_s, n != 0); - } else if (imm == 0) { - if (Rd != Rn) { - MOV(Rd, Rn); - } - } else { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "ORRI2R - more bits in imm than Rn"); + if (!TryORRI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "ORRI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); ORR(Rd, Rn, scratch); @@ -3694,14 +3684,8 @@ void ARM64XEmitter::ORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::EORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - unsigned int n, imm_s, imm_r; - if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { - EOR(Rd, Rn, imm_r, imm_s, n != 0); - } else if (imm == 0) { - if (Rd != Rn) { - MOV(Rd, Rn); - } - } else { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "EORI2R - more bits in imm than Rn"); + if (!TryEORI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "EORI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); EOR(Rd, Rn, scratch); @@ -3709,11 +3693,13 @@ void ARM64XEmitter::EORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::ANDSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { + if (!Is64Bit(Rn)) + imm &= 0xFFFFFFFF; unsigned int n, imm_s, imm_r; if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { ANDS(Rd, Rn, imm_r, imm_s, n != 0); } else if (imm == 0) { - ANDS(Rd, Rn, Is64Bit(Rn) ? ZR : WZR, ArithOption(Rd, ST_LSL, 0)); + ANDS(Rd, Rn, Is64Bit(Rn) ? ZR : WZR); } else { _assert_msg_(JIT, scratch != INVALID_REG, "ANDSI2R - failed to construct logical immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); @@ -3722,11 +3708,7 @@ void ARM64XEmitter::ANDSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::ADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - u32 val; - bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { - ADD(Rd, Rn, val, shift); - } else { + if (!TryADDI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "ADDI2R - failed to construct arithmetic immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); ADD(Rd, Rn, scratch); @@ -3734,11 +3716,7 @@ void ARM64XEmitter::ADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::SUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - u32 val; - bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { - SUB(Rd, Rn, val, shift); - } else { + if (!TrySUBI2R(Rd, Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "SUBI2R - failed to construct arithmetic immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); SUB(Rd, Rn, scratch); @@ -3746,18 +3724,14 @@ void ARM64XEmitter::SUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch) } void ARM64XEmitter::CMPI2R(ARM64Reg Rn, u64 imm, ARM64Reg scratch) { - u32 val; - bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { - CMP(Rn, val, shift); - } else { + if (!TryCMPI2R(Rn, imm)) { _assert_msg_(JIT, scratch != INVALID_REG, "CMPI2R - failed to construct arithmetic immediate value from %08x, need scratch", (u32)imm); MOVI2R(scratch, imm); CMP(Rn, scratch); } } -bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { u32 val; bool shift; if (IsImmArithmetic(imm, &val, &shift)) { @@ -3768,7 +3742,7 @@ bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { } } -bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { u32 val; bool shift; if (IsImmArithmetic(imm, &val, &shift)) { @@ -3779,7 +3753,7 @@ bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { } } -bool ARM64XEmitter::TryCMPI2R(ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryCMPI2R(ARM64Reg Rn, u64 imm) { u32 val; bool shift; if (IsImmArithmetic(imm, &val, &shift)) { @@ -3790,9 +3764,11 @@ bool ARM64XEmitter::TryCMPI2R(ARM64Reg Rn, u32 imm) { } } -bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + if (!Is64Bit(Rn)) + imm &= 0xFFFFFFFF; u32 n, imm_r, imm_s; - if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { + if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { AND(Rd, Rn, imm_r, imm_s, n != 0); return true; } else if (imm == 0) { @@ -3802,9 +3778,10 @@ bool ARM64XEmitter::TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { return false; } } -bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "TryORRI2R - more bits in imm than Rn"); u32 n, imm_r, imm_s; - if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { + if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { ORR(Rd, Rn, imm_r, imm_s, n != 0); return true; } else if (imm == 0) { @@ -3816,9 +3793,10 @@ bool ARM64XEmitter::TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { return false; } } -bool ARM64XEmitter::TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm) { +bool ARM64XEmitter::TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + _assert_msg_(JIT, Is64Bit(Rn) || (imm & 0xFFFFFFFF00000000UL) == 0, "TryEORI2R - more bits in imm than Rn"); u32 n, imm_r, imm_s; - if (IsImmLogical(imm, 32, &n, &imm_s, &imm_r)) { + if (IsImmLogical(imm, Is64Bit(Rn) ? 64 : 32, &n, &imm_s, &imm_r)) { EOR(Rd, Rn, imm_r, imm_s, n != 0); return true; } else if (imm == 0) { diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h index 4a05b9564b..097d82a85a 100644 --- a/Common/Arm64Emitter.h +++ b/Common/Arm64Emitter.h @@ -714,13 +714,13 @@ public: void SUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch = INVALID_REG); void SUBSI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm, ARM64Reg scratch = INVALID_REG); - bool TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TryCMPI2R(ARM64Reg Rn, u32 imm); + bool TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TryCMPI2R(ARM64Reg Rn, u64 imm); - bool TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); - bool TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u32 imm); + bool TryANDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TryORRI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); + bool TryEORI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm); // Pseudo-instruction for convenience. PUSH pushes 16 bytes even though we only push a single register. // This is so the stack pointer is always 16-byte aligned, which is checked by hardware! diff --git a/Core/MIPS/ARM64/Arm64CompALU.cpp b/Core/MIPS/ARM64/Arm64CompALU.cpp index dfa1d8b0d6..2bda625026 100644 --- a/Core/MIPS/ARM64/Arm64CompALU.cpp +++ b/Core/MIPS/ARM64/Arm64CompALU.cpp @@ -57,7 +57,7 @@ static u32 EvalAnd(u32 a, u32 b) { return a & b; } static u32 EvalAdd(u32 a, u32 b) { return a + b; } static u32 EvalSub(u32 a, u32 b) { return a - b; } -void Arm64Jit::CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg src, ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg src, u32 val), u32 (*eval)(u32 a, u32 b)) { +void Arm64Jit::CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg src, ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg src, u64 val), u32 (*eval)(u32 a, u32 b)) { if (gpr.IsImm(rs)) { gpr.SetImm(rt, (*eval)(gpr.GetImm(rs), uimm)); } else { @@ -119,7 +119,7 @@ void Arm64Jit::Comp_IType(MIPSOpcode op) { break; } gpr.MapDirtyIn(rt, rs); - if (!TryCMPI2R(gpr.R(rs), simm)) { + if (!TryCMPI2R(gpr.R(rs), (u32)simm)) { gpr.SetRegImm(SCRATCH1, simm); CMP(gpr.R(rs), SCRATCH1); } @@ -196,7 +196,7 @@ void Arm64Jit::Comp_RType2(MIPSOpcode op) { } } -void Arm64Jit::CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg rm, ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg rm, u32 val), u32(*eval)(u32 a, u32 b), bool symmetric) { +void Arm64Jit::CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arith)(ARM64Reg dst, ARM64Reg rm, ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(ARM64Reg dst, ARM64Reg rm, u64 val), u32(*eval)(u32 a, u32 b), bool symmetric) { if (gpr.IsImm(rs) && gpr.IsImm(rt)) { gpr.SetImm(rd, (*eval)(gpr.GetImm(rs), gpr.GetImm(rt))); return; diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index e8874fa0b5..545de5846e 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -220,8 +220,8 @@ private: void BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely); // Utilities to reduce duplicated code - void CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, Arm64Gen::ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, u32 val), u32 (*eval)(u32 a, u32 b)); - void CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arithOp2)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, Arm64Gen::ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, u32 val), u32 (*eval)(u32 a, u32 b), bool symmetric = false); + void CompImmLogic(MIPSGPReg rs, MIPSGPReg rt, u32 uimm, void (ARM64XEmitter::*arith)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, Arm64Gen::ARM64Reg src2), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg src, u64 val), u32 (*eval)(u32 a, u32 b)); + void CompType3(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, void (ARM64XEmitter::*arithOp2)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, Arm64Gen::ARM64Reg rn), bool (ARM64XEmitter::*tryArithI2R)(Arm64Gen::ARM64Reg dst, Arm64Gen::ARM64Reg rm, u64 val), u32 (*eval)(u32 a, u32 b), bool symmetric = false); void CompShiftImm(MIPSOpcode op, Arm64Gen::ShiftType shiftType, int sa); void CompShiftVar(MIPSOpcode op, Arm64Gen::ShiftType shiftType); void CompVrotShuffle(u8 *dregs, int imm, VectorSize sz, bool negSin); From 56d64f5c67f44ac8721bc0e0ff47582f6ef75e7e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 11:50:29 -0800 Subject: [PATCH 029/131] arm64jit: Avoid temporary on variable shift. I think we should trust that it works per the spec. --- Core/MIPS/ARM64/Arm64CompALU.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompALU.cpp b/Core/MIPS/ARM64/Arm64CompALU.cpp index 2bda625026..93fc77662e 100644 --- a/Core/MIPS/ARM64/Arm64CompALU.cpp +++ b/Core/MIPS/ARM64/Arm64CompALU.cpp @@ -392,13 +392,11 @@ void Arm64Jit::CompShiftVar(MIPSOpcode op, Arm64Gen::ShiftType shiftType) { return; } gpr.MapDirtyInIn(rd, rs, rt); - // Not sure if ARM64 wraps like this so let's do it for it. (TODO: According to the ARM ARM, it will indeed mask for us so this is not necessary) - ANDI2R(SCRATCH1, gpr.R(rs), 0x1F, INVALID_REG); switch (shiftType) { - case ST_LSL: LSLV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; - case ST_LSR: LSRV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; - case ST_ASR: ASRV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; - case ST_ROR: RORV(gpr.R(rd), gpr.R(rt), SCRATCH1); break; + case ST_LSL: LSLV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; + case ST_LSR: LSRV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; + case ST_ASR: ASRV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; + case ST_ROR: RORV(gpr.R(rd), gpr.R(rt), gpr.R(rs)); break; } } From b59c0d0b45db3d98f4627891666bb53867014919 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 12:46:09 -0800 Subject: [PATCH 030/131] arm64jit: Use CMN for CMPI2R if possible. It's not hit all that often, but it is hit. --- Common/Arm64Emitter.cpp | 8 ++++++++ Common/Arm64Emitter.h | 1 + 2 files changed, 9 insertions(+) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index ad834e1872..43dcbe4ba6 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -1547,6 +1547,10 @@ void ARM64XEmitter::CMP(ARM64Reg Rn, u32 imm, bool shift) { EncodeAddSubImmInst(1, true, shift, imm, Rn, Is64Bit(Rn) ? SP : WSP); } +void ARM64XEmitter::CMN(ARM64Reg Rn, u32 imm, bool shift) +{ + EncodeAddSubImmInst(0, true, shift, imm, Rn, Is64Bit(Rn) ? SP : WSP); +} // Data Processing (Immediate) void ARM64XEmitter::MOVZ(ARM64Reg Rd, u32 imm, ShiftAmount pos) @@ -3754,11 +3758,15 @@ bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { } bool ARM64XEmitter::TryCMPI2R(ARM64Reg Rn, u64 imm) { + s64 negated = Is64Bit(Rn) ? -(s64)imm : -(s32)(u32)imm; u32 val; bool shift; if (IsImmArithmetic(imm, &val, &shift)) { CMP(Rn, val, shift); return true; + } else if (IsImmArithmetic((u64)negated, &val, &shift)) { + CMN(Rn, val, shift); + return true; } else { return false; } diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h index 097d82a85a..a6ce09b3fa 100644 --- a/Common/Arm64Emitter.h +++ b/Common/Arm64Emitter.h @@ -591,6 +591,7 @@ public: void SUB(ARM64Reg Rd, ARM64Reg Rn, u32 imm, bool shift = false); void SUBS(ARM64Reg Rd, ARM64Reg Rn, u32 imm, bool shift = false); void CMP(ARM64Reg Rn, u32 imm, bool shift = false); + void CMN(ARM64Reg Rn, u32 imm, bool shift = false); // Data Processing (Immediate) void MOVZ(ARM64Reg Rd, u32 imm, ShiftAmount pos = SHIFT_0); From 7f8a871e306701b19c9adbd7ad6cddb0fe6e9d5e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 12:53:13 -0800 Subject: [PATCH 031/131] arm64jit: Handle more imm compare cases. --- Core/MIPS/ARM64/Arm64CompBranch.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index beb9ab7777..489dca0f4d 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -129,10 +129,20 @@ void Arm64Jit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely) // We might be able to flip the condition (EQ/NEQ are easy.) const bool canFlip = cc == CC_EQ || cc == CC_NEQ; - // TODO ARM64: Optimize for immediates other than zero - if (rt == 0) { - gpr.MapIn(rs); - CMP(gpr.R(rs), 0); + u32 val; + bool shift; + if (gpr.IsImm(rt) && IsImmArithmetic(gpr.GetImm(rt), &val, &shift)) { + gpr.MapReg(rs); + CMP(gpr.R(rs), val, shift); + } else if (gpr.IsImm(rt) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rt), &val, &shift)) { + gpr.MapReg(rs); + CMN(gpr.R(rs), val, shift); + } else if (gpr.IsImm(rs) && IsImmArithmetic(gpr.GetImm(rs), &val, &shift) && canFlip) { + gpr.MapReg(rt); + CMP(gpr.R(rt), val, shift); + } else if (gpr.IsImm(rs) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rs), &val, &shift) && canFlip) { + gpr.MapReg(rt); + CMN(gpr.R(rt), val, shift); } else { gpr.MapInIn(rs, rt); CMP(gpr.R(rs), gpr.R(rt)); From c71285c970ceb6d210dd1a90211a7772d61ad641 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 13:13:07 -0800 Subject: [PATCH 032/131] arm64jit: Use CBZ/CBNZ for zero compare branches. These are pretty common, so it reduces bloat decently. Seems about the same speed, though. --- Core/MIPS/ARM64/Arm64CompBranch.cpp | 75 ++++++++++++++++++----------- Core/MIPS/ARM64/Arm64RegCache.cpp | 3 ++ 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index 489dca0f4d..ba75692896 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -128,36 +128,56 @@ void Arm64Jit::BranchRSRTComp(MIPSOpcode op, CCFlags cc, bool likely) // We might be able to flip the condition (EQ/NEQ are easy.) const bool canFlip = cc == CC_EQ || cc == CC_NEQ; - - u32 val; - bool shift; - if (gpr.IsImm(rt) && IsImmArithmetic(gpr.GetImm(rt), &val, &shift)) { - gpr.MapReg(rs); - CMP(gpr.R(rs), val, shift); - } else if (gpr.IsImm(rt) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rt), &val, &shift)) { - gpr.MapReg(rs); - CMN(gpr.R(rs), val, shift); - } else if (gpr.IsImm(rs) && IsImmArithmetic(gpr.GetImm(rs), &val, &shift) && canFlip) { - gpr.MapReg(rt); - CMP(gpr.R(rt), val, shift); - } else if (gpr.IsImm(rs) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rs), &val, &shift) && canFlip) { - gpr.MapReg(rt); - CMN(gpr.R(rt), val, shift); - } else { - gpr.MapInIn(rs, rt); - CMP(gpr.R(rs), gpr.R(rt)); - } + const bool rsIsZero = gpr.IsImm(rs) && gpr.GetImm(rs) == 0; + const bool rtIsZero = gpr.IsImm(rt) && gpr.GetImm(rt) == 0; Arm64Gen::FixupBranch ptr; - if (!likely) { - if (!delaySlotIsNice) - CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); - else - FlushAll(); - ptr = B(cc); - } else { + if ((likely || delaySlotIsNice) && (rsIsZero || rtIsZero) && canFlip) { + // Special case, we can just use CBZ/CBNZ directly. + MIPSGPReg r = rsIsZero ? rt : rs; + gpr.MapReg(r); + // Flush should keep r in the same armreg. + ARM64Reg ar = gpr.R(r); FlushAll(); - ptr = B(cc); + if (cc == CC_EQ) { + ptr = CBZ(ar); + } else { + ptr = CBNZ(ar); + } + } else { + u32 val; + bool shift; + if (gpr.IsImm(rt) && IsImmArithmetic(gpr.GetImm(rt), &val, &shift)) { + gpr.MapReg(rs); + CMP(gpr.R(rs), val, shift); + } else if (gpr.IsImm(rt) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rt), &val, &shift)) { + gpr.MapReg(rs); + CMN(gpr.R(rs), val, shift); + } else if (gpr.IsImm(rs) && IsImmArithmetic(gpr.GetImm(rs), &val, &shift) && canFlip) { + gpr.MapReg(rt); + CMP(gpr.R(rt), val, shift); + } else if (gpr.IsImm(rs) && IsImmArithmetic((u64)(s64)-(s32)gpr.GetImm(rs), &val, &shift) && canFlip) { + gpr.MapReg(rt); + CMN(gpr.R(rt), val, shift); + } else { + gpr.MapInIn(rs, rt); + CMP(gpr.R(rs), gpr.R(rt)); + } + + if (!likely) { + if (!delaySlotIsNice) + CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); + else + FlushAll(); + ptr = B(cc); + } else { + FlushAll(); + ptr = B(cc); + } + } + + if (likely) { + // Only executed when taking the branch. CompileDelaySlot(DELAYSLOT_FLUSH); } @@ -244,7 +264,6 @@ void Arm64Jit::BranchRSZeroComp(MIPSOpcode op, CCFlags cc, bool andLink, bool li if (!likely && delaySlotIsNice) CompileDelaySlot(DELAYSLOT_NICE); - // TODO: Maybe we could use BZ here? gpr.MapReg(rs); CMP(gpr.R(rs), 0); diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index ca1e8fd03d..903819a74f 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -690,6 +690,9 @@ void Arm64RegCache::FlushR(MIPSGPReg r) { } void Arm64RegCache::FlushAll() { + // Note: make sure not to change the registers when flushing: + // Branching code expects the armreg to retain its value. + // LO can't be included in a 32-bit pair, since it's 64 bit. // Flush it first so we don't get it confused. FlushR(MIPS_REG_LO); From 3b4917a308712d121c59945869f0699b834dca03 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 14:35:31 -0800 Subject: [PATCH 033/131] arm64jit: Use TBZ/TBNZ for fp branches. --- Core/MIPS/ARM64/Arm64CompBranch.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index ba75692896..79fe49cc85 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -353,19 +353,24 @@ void Arm64Jit::BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely) { if (!likely && delaySlotIsNice) CompileDelaySlot(DELAYSLOT_NICE); - // TODO: Maybe we could use TBZ here? gpr.MapReg(MIPS_REG_FPCOND); - TSTI2R(gpr.R(MIPS_REG_FPCOND), 1, SCRATCH1); Arm64Gen::FixupBranch ptr; - if (!likely) { - if (!delaySlotIsNice) - CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); - else - FlushAll(); - ptr = B(cc); - } else { + if (likely || delaySlotIsNice) { + // FlushAll() won't actually change the reg. + ARM64Reg ar = gpr.R(MIPS_REG_FPCOND); FlushAll(); + if (cc == CC_EQ) { + ptr = TBZ(ar, 0); + } else { + ptr = TBNZ(ar, 0); + } + } else { + TSTI2R(gpr.R(MIPS_REG_FPCOND), 1, SCRATCH1); + CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); ptr = B(cc); + } + + if (likely) { CompileDelaySlot(DELAYSLOT_FLUSH); } From ee236743f08cc2d1cb2217cbb057e115d84c8960 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 14:43:53 -0800 Subject: [PATCH 034/131] arm64jit: Use TBZ/TBNZ for vfpu branch as well. --- Core/MIPS/ARM64/Arm64CompBranch.cpp | 32 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index 79fe49cc85..21d71ec819 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -419,28 +419,26 @@ void Arm64Jit::BranchVFPUFlag(MIPSOpcode op, CCFlags cc, bool likely) { int imm3 = (op >> 18) & 7; - // TODO: Maybe could use TBZ? gpr.MapReg(MIPS_REG_VFPUCC); - TSTI2R(gpr.R(MIPS_REG_VFPUCC), 1 << imm3, SCRATCH1); - Arm64Gen::FixupBranch ptr; - js.inDelaySlot = true; - if (!likely) - { - if (!delaySlotIsNice && !delaySlotIsBranch) - CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); - else - FlushAll(); - ptr = B(cc); - } - else - { + if (likely || delaySlotIsNice || delaySlotIsBranch) { + // FlushAll() won't actually change the reg. + ARM64Reg ar = gpr.R(MIPS_REG_VFPUCC); FlushAll(); + if (cc == CC_EQ) { + ptr = TBZ(ar, imm3); + } else { + ptr = TBNZ(ar, imm3); + } + } else { + TSTI2R(gpr.R(MIPS_REG_VFPUCC), 1 << imm3, SCRATCH1); + CompileDelaySlot(DELAYSLOT_SAFE_FLUSH); ptr = B(cc); - if (!delaySlotIsBranch) - CompileDelaySlot(DELAYSLOT_FLUSH); } - js.inDelaySlot = false; + + if (likely && !delaySlotIsBranch) { + CompileDelaySlot(DELAYSLOT_FLUSH); + } // Take the branch WriteExit(targetAddr, js.nextExit++); From 98ed6fab3f87ec91cdb3755756222c10bbfcf83d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 17:05:37 -0800 Subject: [PATCH 035/131] arm64jit: Fix spilling for more than one temp reg. Otherwise we hang trying to spill the same reg over and over. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 903819a74f..f9c8167784 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -275,6 +275,8 @@ ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { ARM64Reg reg = allocOrder[i]; if (ar[reg].mipsReg != MIPS_REG_INVALID && mr[ar[reg].mipsReg].spillLock) continue; + if (ar[reg].tempLocked) + continue; // As it's in alloc-order, we know it's not static so we don't need to check for that. From c00044c5d853b5096e35576de60ffd0e048c1530 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 17:06:29 -0800 Subject: [PATCH 036/131] arm64jit: Avoid arithmetic movs. ORR is the preferred encoding and may be faster on some chips. --- Common/Arm64Emitter.cpp | 12 ++++++++++-- Core/MIPS/ARM64/Arm64CompALU.cpp | 11 ++++++++++- Core/MIPS/ARM64/Arm64CompBranch.cpp | 2 +- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 43dcbe4ba6..52bf53e1f7 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -3738,7 +3738,11 @@ void ARM64XEmitter::CMPI2R(ARM64Reg Rn, u64 imm, ARM64Reg scratch) { bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { u32 val; bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { + if (imm == 0) { + // Prefer MOV (ORR) instead of ADD for moves. + MOV(Rd, Rn); + return true; + } else if (IsImmArithmetic(imm, &val, &shift)) { ADD(Rd, Rn, val, shift); return true; } else { @@ -3749,7 +3753,11 @@ bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { u32 val; bool shift; - if (IsImmArithmetic(imm, &val, &shift)) { + if (imm == 0) { + // Prefer MOV (ORR) instead of ADD for moves. + MOV(Rd, Rn); + return true; + } else if (IsImmArithmetic(imm, &val, &shift)) { SUB(Rd, Rn, val, shift); return true; } else { diff --git a/Core/MIPS/ARM64/Arm64CompALU.cpp b/Core/MIPS/ARM64/Arm64CompALU.cpp index 93fc77662e..e9400f1efa 100644 --- a/Core/MIPS/ARM64/Arm64CompALU.cpp +++ b/Core/MIPS/ARM64/Arm64CompALU.cpp @@ -262,7 +262,16 @@ void Arm64Jit::Comp_RType3(MIPSOpcode op) { case 32: //R(rd) = R(rs) + R(rt); break; //add case 33: //R(rd) = R(rs) + R(rt); break; //addu - CompType3(rd, rs, rt, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd, true); + if (gpr.IsImm(rs) && gpr.GetImm(rs) == 0 && !gpr.IsImm(rt)) { + // Special case: actually a mov, avoid arithmetic. + gpr.MapDirtyIn(rd, rt); + MOV(gpr.R(rd), gpr.R(rt)); + } else if (gpr.IsImm(rt) && gpr.GetImm(rt) == 0 && !gpr.IsImm(rs)) { + gpr.MapDirtyIn(rd, rs); + MOV(gpr.R(rd), gpr.R(rs)); + } else { + CompType3(rd, rs, rt, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd, true); + } break; case 34: //R(rd) = R(rs) - R(rt); break; //sub diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index 21d71ec819..ea9acffa6f 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -574,7 +574,7 @@ void Arm64Jit::Comp_JumpReg(MIPSOpcode op) destReg = gpr.R(rs); // Safe because FlushAll doesn't change any regs FlushAll(); } else { - // Delay slot - this case is very rare, might be able to free up R8. + // Delay slot - this case is very rare, might be able to free up W24. gpr.MapReg(rs); MOV(destReg, gpr.R(rs)); if (andLink) From 0fc8274ec4e084297aba95c38f106320888df78c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 17:15:21 -0800 Subject: [PATCH 037/131] arm64jit: Enable safe memory for lwl/lwr. --- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 5219c1a239..856d3e71cb 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -176,18 +176,18 @@ namespace MIPSComp { } else { gpr.MapInIn(rt, rs); } + gpr.SpillLock(rt); + gpr.SpillLock(rs); + // Need to get temps before skipping safe mem. + ARM64Reg LR_SCRATCH3 = gpr.GetAndLockTempR(); + ARM64Reg LR_SCRATCH4 = gpr.GetAndLockTempR(); - if (false && !g_Config.bFastMemory && rs != MIPS_REG_SP) { + if (!g_Config.bFastMemory && rs != MIPS_REG_SP) { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); } else { SetScratch1ToEffectiveAddress(rs, offset); } - // We can lose rs now, since we have it in SCRATCH1. - gpr.SpillLock(rt); - ARM64Reg LR_SCRATCH3 = gpr.GetAndLockTempR(); - ARM64Reg LR_SCRATCH4 = gpr.GetAndLockTempR(); - // Here's our shift amount. ANDI2R(SCRATCH2, SCRATCH1, 3); LSL(SCRATCH2, SCRATCH2, 3); From b9cd88381154d7115dbc69efeaa4c5ebf0936ec5 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 29 Dec 2017 17:55:37 -0800 Subject: [PATCH 038/131] UI: Only default focus visible things. Also, default focus perm button. --- UI/MainScreen.cpp | 10 ++++++++-- ext/native/ui/ui_screen.cpp | 5 +++-- ext/native/ui/viewgroup.cpp | 4 +++- ext/native/ui/viewgroup.h | 8 ++++---- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 3ec5953109..f3276a41e8 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -762,6 +762,7 @@ void MainScreen::CreateViews() { tabRecentGames->OnHighlight.Handle(this, &MainScreen::OnGameHighlight); } + Button *grantStorageButton = nullptr; if (hasStorageAccess) { ScrollView *scrollAllGames = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); scrollAllGames->SetTag("MainScreenAllGames"); @@ -819,7 +820,8 @@ void MainScreen::CreateViews() { LinearLayout *buttonHolder = new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); - buttonHolder->Add(new Button(mm->T("Give PPSSPP permission to access storage"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)))->OnClick.Handle(this, &MainScreen::OnAllowStorage); + grantStorageButton = new Button(mm->T("Give PPSSPP permission to access storage"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + buttonHolder->Add(grantStorageButton)->OnClick.Handle(this, &MainScreen::OnAllowStorage); buttonHolder->Add(new Spacer(new LinearLayoutParams(1.0f))); leftColumn->Add(new Spacer(new LinearLayoutParams(0.1f))); @@ -878,7 +880,11 @@ void MainScreen::CreateViews() { root_->Add(rightColumn); } - root_->SetDefaultFocusView(tabHolder_); + if (grantStorageButton) { + root_->SetDefaultFocusView(grantStorageButton); + } else if (tabHolder_->GetVisibility() != V_GONE) { + root_->SetDefaultFocusView(tabHolder_); + } I18NCategory *u = GetI18NCategory("Upgrade"); diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index 7581e5fcdc..6d8c81e4e1 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -33,8 +33,9 @@ void UIScreen::DoRecreateViews() { delete root_; root_ = nullptr; CreateViews(); - if (root_ && root_->GetDefaultFocusView()) { - root_->GetDefaultFocusView()->SetFocus(); + UI::View *defaultView = root_ ? root_->GetDefaultFocusView() : nullptr; + if (defaultView && defaultView->GetVisibility() == UI::V_VISIBLE) { + defaultView->SetFocus(); } recreateViews_ = false; diff --git a/ext/native/ui/viewgroup.cpp b/ext/native/ui/viewgroup.cpp index 5a9b4b425b..b6e9c08dcc 100644 --- a/ext/native/ui/viewgroup.cpp +++ b/ext/native/ui/viewgroup.cpp @@ -1555,7 +1555,9 @@ void UpdateViewHierarchy(ViewGroup *root) { std::lock_guard lock(focusLock); EnableFocusMovement(true); if (!GetFocusedView()) { - if (root->GetDefaultFocusView()) { + View *defaultView = root->GetDefaultFocusView(); + // Can't focus what you can't see. + if (defaultView && defaultView->GetVisibility() == V_VISIBLE) { root->GetDefaultFocusView()->SetFocus(); } else { root->SetFocus(); diff --git a/ext/native/ui/viewgroup.h b/ext/native/ui/viewgroup.h index b7de8c539c..6ede1fb9d2 100644 --- a/ext/native/ui/viewgroup.h +++ b/ext/native/ui/viewgroup.h @@ -24,7 +24,7 @@ struct NeighborResult { class ViewGroup : public View { public: - ViewGroup(LayoutParams *layoutParams = 0) : View(layoutParams), defaultFocusView_(0), hasDropShadow_(false), clip_(false) {} + ViewGroup(LayoutParams *layoutParams = 0) : View(layoutParams) {} virtual ~ViewGroup(); // Pass through external events to children. @@ -83,11 +83,11 @@ public: protected: std::mutex modifyLock_; // Hold this when changing the subviews. std::vector views_; - View *defaultFocusView_; + View *defaultFocusView_ = nullptr; Drawable bg_; float dropShadowExpand_ = 0.0f; - bool hasDropShadow_; - bool clip_; + bool hasDropShadow_ = false; + bool clip_ = false; }; // A frame layout contains a single child view (normally). From 5ea54d991ed16fbf9d4e48d3ca8e02525c48f723 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 01:10:17 -0800 Subject: [PATCH 039/131] UI: Fix dev menu scroll on small screens. The scroll thought it could be as tall as the screen, due to FILL_PARENT. Maybe that should always use a weight... --- UI/DevScreens.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index cdb8d27a78..6281d6f99e 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -60,7 +60,7 @@ void DevMenu::CreatePopupContents(UI::ViewGroup *parent) { I18NCategory *dev = GetI18NCategory("Developer"); I18NCategory *sy = GetI18NCategory("System"); - ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, 1.0f)); LinearLayout *items = new LinearLayout(ORIENT_VERTICAL); #if !defined(MOBILE_DEVICE) From e7ac6725224e9afb945b7abe048add2aec83c8ac Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 00:31:46 -0800 Subject: [PATCH 040/131] arm64jit: Cleanup method names, temp discard. This way MapDirtyIn won't accidentally discard temps. --- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 10 +++++----- Core/MIPS/ARM64/Arm64RegCache.cpp | 26 ++++++++++++++++---------- Core/MIPS/ARM64/Arm64RegCache.h | 7 ++++--- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 856d3e71cb..a3f44e9e89 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -250,7 +250,7 @@ namespace MIPSComp { SetJumpTarget(skip); } - gpr.ReleaseSpillLocks(); + gpr.ReleaseSpillLocksAndDiscardTemps(); } void Arm64Jit::Comp_ITypeMem(MIPSOpcode op) { @@ -311,7 +311,7 @@ namespace MIPSComp { gpr.MapRegAsPointer(rs); // For a store, try to avoid mapping a reg if not needed. - targetReg = load ? INVALID_REG : gpr.MapTempImm(rt); + targetReg = load ? INVALID_REG : gpr.TryMapTempImm(rt); if (targetReg == INVALID_REG) { gpr.MapReg(rt, load ? MAP_NOINIT : 0); targetReg = gpr.R(rt); @@ -327,12 +327,12 @@ namespace MIPSComp { case 41: STRH(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; case 40: STRB(INDEX_UNSIGNED, targetReg, gpr.RPtr(rs), offset); break; } - gpr.ReleaseSpillLocks(); + gpr.ReleaseSpillLocksAndDiscardTemps(); break; } } - if (!load && gpr.IsImm(rt) && gpr.MapTempImm(rt) != INVALID_REG) { + if (!load && gpr.IsImm(rt) && gpr.TryMapTempImm(rt) != INVALID_REG) { // We're storing an immediate value, let's see if we can optimize rt. if (!gpr.IsImm(rs) || offset == 0) { // In this case, we're always going to need rs mapped, which may flush the temp imm. @@ -340,7 +340,7 @@ namespace MIPSComp { gpr.MapIn(rs); } - targetReg = gpr.MapTempImm(rt); + targetReg = gpr.TryMapTempImm(rt); } if (gpr.IsImm(rs) && Memory::IsValidAddress(iaddr)) { diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index f9c8167784..0111ec00d2 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -304,7 +304,7 @@ ARM64Reg Arm64RegCache::FindBestToSpill(bool unusedOnly, bool *clobbered) { return INVALID_REG; } -ARM64Reg Arm64RegCache::MapTempImm(MIPSGPReg r) { +ARM64Reg Arm64RegCache::TryMapTempImm(MIPSGPReg r) { // If already mapped, no need for a temporary. if (IsMapped(r)) { return R(r); @@ -459,7 +459,7 @@ void Arm64RegCache::MapInIn(MIPSGPReg rd, MIPSGPReg rs) { SpillLock(rd, rs); MapReg(rd); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd, rs); } void Arm64RegCache::MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad) { @@ -467,7 +467,7 @@ void Arm64RegCache::MapDirtyIn(MIPSGPReg rd, MIPSGPReg rs, bool avoidLoad) { bool load = !avoidLoad || rd == rs; MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd, rs); } void Arm64RegCache::MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) { @@ -476,7 +476,7 @@ void Arm64RegCache::MapDirtyInIn(MIPSGPReg rd, MIPSGPReg rs, MIPSGPReg rt, bool MapReg(rd, load ? MAP_DIRTY : MAP_NOINIT); MapReg(rt); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd, rs, rt); } void Arm64RegCache::MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, bool avoidLoad) { @@ -486,7 +486,7 @@ void Arm64RegCache::MapDirtyDirtyIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MapReg(rd1, load1 ? MAP_DIRTY : MAP_NOINIT); MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd1, rd2, rs); } void Arm64RegCache::MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs, MIPSGPReg rt, bool avoidLoad) { @@ -497,7 +497,7 @@ void Arm64RegCache::MapDirtyDirtyInIn(MIPSGPReg rd1, MIPSGPReg rd2, MIPSGPReg rs MapReg(rd2, load2 ? MAP_DIRTY : MAP_NOINIT); MapReg(rt); MapReg(rs); - ReleaseSpillLocks(); + ReleaseSpillLock(rd1, rd2, rs, rt); } void Arm64RegCache::FlushArmReg(ARM64Reg r) { @@ -872,7 +872,7 @@ void Arm64RegCache::SpillLock(MIPSGPReg r1, MIPSGPReg r2, MIPSGPReg r3, MIPSGPRe if (r4 != MIPS_REG_INVALID) mr[r4].spillLock = true; } -void Arm64RegCache::ReleaseSpillLocks() { +void Arm64RegCache::ReleaseSpillLocksAndDiscardTemps() { for (int i = 0; i < NUM_MIPSREG; i++) { if (!mr[i].isStatic) mr[i].spillLock = false; @@ -882,9 +882,15 @@ void Arm64RegCache::ReleaseSpillLocks() { } } -void Arm64RegCache::ReleaseSpillLock(MIPSGPReg reg) { - if (!mr[reg].isStatic) - mr[reg].spillLock = false; +void Arm64RegCache::ReleaseSpillLock(MIPSGPReg r1, MIPSGPReg r2, MIPSGPReg r3, MIPSGPReg r4) { + if (!mr[r1].isStatic) + mr[r1].spillLock = false; + if (r2 != MIPS_REG_INVALID && !mr[r2].isStatic) + mr[r2].spillLock = false; + if (r3 != MIPS_REG_INVALID && !mr[r3].isStatic) + mr[r3].spillLock = false; + if (r4 != MIPS_REG_INVALID && !mr[r4].isStatic) + mr[r4].spillLock = false; } ARM64Reg Arm64RegCache::R(MIPSGPReg mipsReg) { diff --git a/Core/MIPS/ARM64/Arm64RegCache.h b/Core/MIPS/ARM64/Arm64RegCache.h index b81f4bbb0c..603dca1289 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.h +++ b/Core/MIPS/ARM64/Arm64RegCache.h @@ -92,8 +92,8 @@ public: // Protect the arm register containing a MIPS register from spilling, to ensure that // it's being kept allocated. void SpillLock(MIPSGPReg reg, MIPSGPReg reg2 = MIPS_REG_INVALID, MIPSGPReg reg3 = MIPS_REG_INVALID, MIPSGPReg reg4 = MIPS_REG_INVALID); - void ReleaseSpillLock(MIPSGPReg reg); - void ReleaseSpillLocks(); + void ReleaseSpillLock(MIPSGPReg reg, MIPSGPReg reg2 = MIPS_REG_INVALID, MIPSGPReg reg3 = MIPS_REG_INVALID, MIPSGPReg reg4 = MIPS_REG_INVALID); + void ReleaseSpillLocksAndDiscardTemps(); void SetImm(MIPSGPReg reg, u64 immVal); bool IsImm(MIPSGPReg reg) const; @@ -102,7 +102,8 @@ public: // Optimally set a register to an imm value (possibly using another register.) void SetRegImm(Arm64Gen::ARM64Reg reg, u64 imm); - Arm64Gen::ARM64Reg MapTempImm(MIPSGPReg); + // May fail and return INVALID_REG if it needs flushing. + Arm64Gen::ARM64Reg TryMapTempImm(MIPSGPReg); // Returns an ARM register containing the requested MIPS register. Arm64Gen::ARM64Reg MapReg(MIPSGPReg reg, int mapFlags = 0); From 89cbf36611daa0bebb5d2d75b2fba5b7a3139b6b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 00:32:31 -0800 Subject: [PATCH 041/131] arm64jit: Free up W23 for static alloc. We shouldn't always reserve W23 for this uncommon case. --- Core/MIPS/ARM64/Arm64CompBranch.cpp | 6 ++++-- Core/MIPS/ARM64/Arm64RegCache.h | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index ea9acffa6f..9d8f41fb8c 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -536,7 +536,7 @@ void Arm64Jit::Comp_JumpReg(MIPSOpcode op) delaySlotIsNice = false; CONDITIONAL_NICE_DELAYSLOT; - ARM64Reg destReg = OTHERTEMPREG; + ARM64Reg destReg = INVALID_REG; if (IsSyscall(delaySlotOp)) { gpr.MapReg(rs); MovToPC(gpr.R(rs)); // For syscall to be able to return. @@ -574,7 +574,9 @@ void Arm64Jit::Comp_JumpReg(MIPSOpcode op) destReg = gpr.R(rs); // Safe because FlushAll doesn't change any regs FlushAll(); } else { - // Delay slot - this case is very rare, might be able to free up W24. + // Since we can't be in a delay slot, should be safe to steal FLAGTEMPREG for a temp reg. + // It will be saved, even if a function is called. + destReg = FLAGTEMPREG; gpr.MapReg(rs); MOV(destReg, gpr.R(rs)); if (andLink) diff --git a/Core/MIPS/ARM64/Arm64RegCache.h b/Core/MIPS/ARM64/Arm64RegCache.h index 603dca1289..b95caf7854 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.h +++ b/Core/MIPS/ARM64/Arm64RegCache.h @@ -23,8 +23,7 @@ namespace Arm64JitConstants { -const Arm64Gen::ARM64Reg DOWNCOUNTREG = Arm64Gen::W23; -const Arm64Gen::ARM64Reg OTHERTEMPREG = Arm64Gen::W24; +const Arm64Gen::ARM64Reg DOWNCOUNTREG = Arm64Gen::W24; const Arm64Gen::ARM64Reg FLAGTEMPREG = Arm64Gen::X25; const Arm64Gen::ARM64Reg JITBASEREG = Arm64Gen::X26; const Arm64Gen::ARM64Reg CTXREG = Arm64Gen::X27; From 267b9a65986587e1ca9b4af94a0ab682731d3b2d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 10:12:10 -0800 Subject: [PATCH 042/131] Profiler: Separately track texture hashing. Since it's slow and was lumped into updatestate. --- GPU/Common/TextureCacheCommon.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 6de0609107..1485a333a3 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include "profiler/profiler.h" #include "Common/ColorConv.h" #include "Common/MemoryUtil.h" #include "Core/Config.h" @@ -1472,6 +1473,7 @@ void TextureCacheCommon::ApplyTexture() { } if (nextNeedsRehash_) { + PROFILE_THIS_SCOPE("texhash"); // Update the hash on the texture. int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); @@ -1547,7 +1549,11 @@ void TextureCacheCommon::DeleteTexture(TexCache::iterator it) { bool TextureCacheCommon::CheckFullHash(TexCacheEntry *entry, bool &doDelete) { int w = gstate.getTextureWidth(0); int h = gstate.getTextureHeight(0); - u32 fullhash = QuickTexHash(replacer_, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + u32 fullhash; + { + PROFILE_THIS_SCOPE("texhash"); + fullhash = QuickTexHash(replacer_, entry->addr, entry->bufw, w, h, GETextureFormat(entry->format), entry); + } if (fullhash == entry->fullhash) { if (g_Config.bTextureBackoffCache) { From 4871b1eeb08d217a36216880fe302a103cfdf881 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 10:19:15 -0800 Subject: [PATCH 043/131] Profiler: Fix cat display on small screens. --- UI/ProfilerDraw.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UI/ProfilerDraw.cpp b/UI/ProfilerDraw.cpp index f90427df44..a4ffb8bbb3 100644 --- a/UI/ProfilerDraw.cpp +++ b/UI/ProfilerDraw.cpp @@ -94,6 +94,10 @@ void DrawProfile(UIContext &ui) { } legendWidth += 20.0f; + if (legendHeight > ui.GetBounds().h) { + legendHeight = ui.GetBounds().h; + } + float legendStartY = legendHeight > ui.GetBounds().centerY() ? ui.GetBounds().y2() - legendHeight : ui.GetBounds().centerY(); float legendStartX = ui.GetBounds().x2() - std::min(legendWidth, 200.0f); From 52c42938961565f590a9a0f79b5a2e1e2969bdc7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 10:45:51 -0800 Subject: [PATCH 044/131] Profiler: Show the slowest thread's categories. May or may not be ideal, but this was the easiest solution... --- UI/ProfilerDraw.cpp | 8 +- ext/native/profiler/profiler.cpp | 164 +++++++++++++++++++++++-------- ext/native/profiler/profiler.h | 16 +-- 3 files changed, 139 insertions(+), 49 deletions(-) diff --git a/UI/ProfilerDraw.cpp b/UI/ProfilerDraw.cpp index a4ffb8bbb3..8d7cf74021 100644 --- a/UI/ProfilerDraw.cpp +++ b/UI/ProfilerDraw.cpp @@ -55,6 +55,7 @@ void DrawProfile(UIContext &ui) { #ifdef USE_PROFILER PROFILE_THIS_SCOPE("timing"); int numCategories = Profiler_GetNumCategories(); + int numThreads = Profiler_GetNumThreads(); int historyLength = Profiler_GetHistoryLength(); ui.SetFontStyle(ui.theme->uiFont); @@ -63,8 +64,11 @@ void DrawProfile(UIContext &ui) { float legendMinVal = lastMaxVal * (1.0f / 120.0f); std::vector history(historyLength); + std::vector slowestThread(historyLength); std::vector catStatus(numCategories); + Profiler_GetSlowestThreads(&slowestThread[0], historyLength); + float rowH = 30.0f; float legendHeight = 0.0f; float legendWidth = 80.0f; @@ -75,7 +79,7 @@ void DrawProfile(UIContext &ui) { continue; } - Profiler_GetHistory(i, &history[0], historyLength); + Profiler_GetSlowestHistory(i, &slowestThread[0], &history[0], historyLength); catStatus[i] = PROFILE_CAT_NOLEGEND; for (int j = 0; j < historyLength; ++j) { if (history[j] > legendMinVal) { @@ -153,7 +157,7 @@ void DrawProfile(UIContext &ui) { if (catStatus[i] == PROFILE_CAT_IGNORE) { continue; } - Profiler_GetHistory(i, &history[0], historyLength); + Profiler_GetSlowestHistory(i, &slowestThread[0], &history[0], historyLength); float x = 10; uint32_t col = nice_colors[i % ARRAY_SIZE(nice_colors)]; diff --git a/ext/native/profiler/profiler.cpp b/ext/native/profiler/profiler.cpp index d48ad7ebbf..35e270b477 100644 --- a/ext/native/profiler/profiler.cpp +++ b/ext/native/profiler/profiler.cpp @@ -1,8 +1,10 @@ // Ultra-lightweight category profiler with history. -#include -#include +#include +#include #include +#include +#include #include @@ -11,8 +13,9 @@ #include "gfx_es2/draw_buffer.h" #include "profiler/profiler.h" -#define MAX_CATEGORIES 32 // Can be any number -#define MAX_DEPTH 16 // Can be any number +#define MAX_CATEGORIES 64 // Can be any number, represents max profiled names. +#define MAX_DEPTH 16 // Can be any number, represents max nesting depth of profiled names. +#define MAX_THREADS 4 // Can be any number, represents concurrent threads calling the profiler. #define HISTORY_SIZE 128 // Must be power of 2 #ifndef _DEBUG @@ -27,33 +30,63 @@ struct Category { struct CategoryFrame { CategoryFrame() { memset(time_taken, 0, sizeof(time_taken)); + memset(count, 0, sizeof(count)); } float time_taken[MAX_CATEGORIES]; int count[MAX_CATEGORIES]; }; struct Profiler { - int frameCount; int historyPos; - int depth; - int parentCategory[MAX_DEPTH]; - double eventStart[MAX_CATEGORIES]; + int depth[MAX_THREADS]; + int parentCategory[MAX_THREADS][MAX_DEPTH]; + double eventStart[MAX_THREADS][MAX_CATEGORIES]; double curFrameStart; }; static Profiler profiler; static Category categories[MAX_CATEGORIES]; +static std::mutex categoriesLock; +static int threadIdAfterLast = 0; +static std::mutex threadsLock; static CategoryFrame *history; +#if MAX_THREADS > 1 +thread_local int profilerThreadId = -1; +#else +static int profilerThreadId = 0; +#endif void internal_profiler_init() { memset(&profiler, 0, sizeof(profiler)); - for (int i = 0; i < MAX_DEPTH; i++) { - profiler.parentCategory[i] = -1; +#if MAX_THREADS == 1 + threadIdAfterLast = 1; +#endif + for (int i = 0; i < MAX_THREADS; i++) { + for (int j = 0; j < MAX_DEPTH; j++) { + profiler.parentCategory[i][j] = -1; + } } - history = new CategoryFrame[HISTORY_SIZE]; + history = new CategoryFrame[HISTORY_SIZE * MAX_THREADS]; } -int internal_profiler_find_cat(const char *category_name) { +static int internal_profiler_find_thread() { + int thread_id = profilerThreadId; + if (thread_id != -1) { + return thread_id; + } + + std::lock_guard guard(threadsLock); + if (threadIdAfterLast < MAX_THREADS) { + thread_id = threadIdAfterLast++; + profilerThreadId = thread_id; + return thread_id; + } + + // Just keep reusing the last one. + return threadIdAfterLast - 1; +} + +int internal_profiler_find_cat(const char *category_name, bool create_missing) { int i; for (i = 0; i < MAX_CATEGORIES; i++) { const char *catname = categories[i].name; @@ -68,8 +101,11 @@ int internal_profiler_find_cat(const char *category_name) { } } - if (i < MAX_CATEGORIES && category_name) { - categories[i].name = category_name; + if (i < MAX_CATEGORIES && category_name && create_missing) { + std::lock_guard guard(categoriesLock); + int race_check = internal_profiler_find_cat(category_name, false); + if (race_check == -1) + categories[i].name = category_name; return i; } @@ -77,79 +113,86 @@ int internal_profiler_find_cat(const char *category_name) { } // Suspend, also used to prepare for leaving. -static void internal_profiler_suspend(int category, double now) { - double diff = now - profiler.eventStart[category]; - history[profiler.historyPos].time_taken[category] += (float)diff; - profiler.eventStart[category] = 0.0; +static void internal_profiler_suspend(int thread_id, int category, double now) { + double diff = now - profiler.eventStart[thread_id][category]; + history[MAX_THREADS * profiler.historyPos + thread_id].time_taken[category] += (float)diff; + profiler.eventStart[thread_id][category] = 0.0; } // Resume, also used as part of entering. -static void internal_profiler_resume(int category, double now) { - profiler.eventStart[category] = now; +static void internal_profiler_resume(int thread_id, int category, double now) { + profiler.eventStart[thread_id][category] = now; } -int internal_profiler_enter(const char *category_name) { - int category = internal_profiler_find_cat(category_name); +int internal_profiler_enter(const char *category_name, int *out_thread_id) { + int category = internal_profiler_find_cat(category_name, true); + int thread_id = internal_profiler_find_thread(); if (category == -1 || !history) { return category; } - if (profiler.eventStart[category] == 0.0f) { + int &depth = profiler.depth[thread_id]; + if (profiler.eventStart[thread_id][category] == 0.0f) { double now = real_time_now(); - int parent = profiler.parentCategory[profiler.depth]; + int parent = profiler.parentCategory[thread_id][depth]; // Temporarily suspend the parent on entering a child. if (parent != -1) { - internal_profiler_suspend(parent, now); + internal_profiler_suspend(thread_id, parent, now); } - internal_profiler_resume(category, now); + internal_profiler_resume(thread_id, category, now); } else { DLOG("profiler: recursive enter (%i - %s)", category, category_name); } - profiler.depth++; - profiler.parentCategory[profiler.depth] = category; + depth++; + profiler.parentCategory[thread_id][depth] = category; + *out_thread_id = thread_id; return category; } -void internal_profiler_leave(int category) { +void internal_profiler_leave(int thread_id, int category) { if (category == -1 || !history) { return; } + + int &depth = profiler.depth[thread_id]; if (category < 0 || category >= MAX_CATEGORIES) { ELOG("Bad category index %d", category); - profiler.depth--; + depth--; return; } double now = real_time_now(); - profiler.depth--; - if (profiler.depth < 0) { + depth--; + if (depth < 0) { FLOG("Profiler enter/leave mismatch!"); } - int parent = profiler.parentCategory[profiler.depth]; + int parent = profiler.parentCategory[thread_id][depth]; // When there's recursion, we don't suspend or resume. if (parent != category) { - internal_profiler_suspend(category, now); - history[profiler.historyPos].count[category]++; + internal_profiler_suspend(thread_id, category, now); + history[MAX_THREADS * profiler.historyPos + thread_id].count[category]++; if (parent != -1) { // Resume tracking the parent. - internal_profiler_resume(parent, now); + internal_profiler_resume(thread_id, parent, now); } } } void internal_profiler_end_frame() { - if (profiler.depth != 0) { + int thread_id = internal_profiler_find_thread(); + if (profiler.depth[thread_id] != 0) { + // Threads may be off, but they'll fall into another frame. FLOG("Can't be inside a profiler scope at end of frame!"); } profiler.curFrameStart = real_time_now(); profiler.historyPos++; profiler.historyPos &= (HISTORY_SIZE - 1); - memset(&history[profiler.historyPos], 0, sizeof(history[profiler.historyPos])); + memset(&history[MAX_THREADS * profiler.historyPos], 0, sizeof(CategoryFrame) * MAX_THREADS); } const char *Profiler_GetCategoryName(int i) { @@ -168,13 +211,54 @@ int Profiler_GetNumCategories() { return 0; } -void Profiler_GetHistory(int category, float *data, int count) { +int Profiler_GetNumThreads() { + return threadIdAfterLast; +} + +void Profiler_GetSlowestThreads(int *data, int count) { + int numCategories = Profiler_GetNumCategories(); for (int i = 0; i < HISTORY_SIZE; i++) { int x = i - count + profiler.historyPos + 1; while (x < 0) x += HISTORY_SIZE; while (x >= HISTORY_SIZE) x -= HISTORY_SIZE; - data[i] = history[x].time_taken[category]; + + float slowestTime = 0.0f; + data[i] = 0; + for (int thread = 0; thread < threadIdAfterLast; ++thread) { + float sum = 0.0f; + for (int c = 0; c < numCategories; ++c) { + sum += history[MAX_THREADS * x + thread].time_taken[c]; + } + if (sum > slowestTime) { + slowestTime = sum; + data[i] = thread; + } + } + } +} + +void Profiler_GetSlowestHistory(int category, int *slowestThreads, float *data, int count) { + for (int i = 0; i < HISTORY_SIZE; i++) { + int x = i - count + profiler.historyPos + 1; + while (x < 0) + x += HISTORY_SIZE; + while (x >= HISTORY_SIZE) + x -= HISTORY_SIZE; + + int thread = slowestThreads[i]; + data[i] = history[MAX_THREADS * x + thread].time_taken[category]; + } +} + +void Profiler_GetHistory(int category, int thread, float *data, int count) { + for (int i = 0; i < HISTORY_SIZE; i++) { + int x = i - count + profiler.historyPos + 1; + while (x < 0) + x += HISTORY_SIZE; + while (x >= HISTORY_SIZE) + x -= HISTORY_SIZE; + data[i] = history[MAX_THREADS * x + thread].time_taken[category]; } } diff --git a/ext/native/profiler/profiler.h b/ext/native/profiler/profiler.h index f615e2e88c..e5addb1ac4 100644 --- a/ext/native/profiler/profiler.h +++ b/ext/native/profiler/profiler.h @@ -2,8 +2,6 @@ #include -// NOTE : This profiler is very single-threaded. Cannot be used from multiple threads yet. - // #define USE_PROFILER #ifdef USE_PROFILER @@ -13,24 +11,28 @@ class DrawBuffer; void internal_profiler_init(); void internal_profiler_end_frame(); -int internal_profiler_enter(const char *category_name); // Returns the category number. -void internal_profiler_leave(int category); +int internal_profiler_enter(const char *category_name, int *thread_id); // Returns the category number. +void internal_profiler_leave(int thread_id, int category); const char *Profiler_GetCategoryName(int i); int Profiler_GetNumCategories(); int Profiler_GetHistoryLength(); -void Profiler_GetHistory(int i, float *data, int count); +int Profiler_GetNumThreads(); +void Profiler_GetSlowestThreads(int *data, int count); +void Profiler_GetSlowestHistory(int category, int *slowestThreads, float *data, int count); +void Profiler_GetHistory(int category, int thread, float *data, int count); class ProfileThis { public: ProfileThis(const char *category) { - cat_ = internal_profiler_enter(category); + cat_ = internal_profiler_enter(category, &thread_); } ~ProfileThis() { - internal_profiler_leave(cat_); + internal_profiler_leave(thread_, cat_); } private: int cat_; + int thread_; }; #define PROFILE_INIT() internal_profiler_init(); From ae636283602cecd02dd125d9faf897a667b40b8f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 00:34:36 -0800 Subject: [PATCH 045/131] arm64jit: Statically allocate ra as well. This doesn't seem to have a significant impact on performance, but it improves bloat by about 5%. --- Core/MIPS/ARM64/Arm64RegCache.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64RegCache.cpp b/Core/MIPS/ARM64/Arm64RegCache.cpp index 0111ec00d2..dd2c83c302 100644 --- a/Core/MIPS/ARM64/Arm64RegCache.cpp +++ b/Core/MIPS/ARM64/Arm64RegCache.cpp @@ -69,10 +69,10 @@ void Arm64RegCache::Start(MIPSAnalyst::AnalysisResults &stats) { const ARM64Reg *Arm64RegCache::GetMIPSAllocationOrder(int &count) { // See register alloc remarks in Arm64Asm.cpp - // W19-W22 are most suitable for static allocation. Those that are chosen for static allocation + // W19-W23 are most suitable for static allocation. Those that are chosen for static allocation // should be omitted here and added in GetStaticAllocations. static const ARM64Reg allocationOrder[] = { - W19, W20, W21, W22, W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, + W19, W20, W21, W22, W23, W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, }; static const ARM64Reg allocationOrderStaticAlloc[] = { W0, W1, W2, W3, W4, W5, W6, W7, W8, W9, W10, W11, W12, W13, W14, W15, @@ -93,6 +93,7 @@ const Arm64RegCache::StaticAllocation *Arm64RegCache::GetStaticAllocations(int & {MIPS_REG_V0, W20}, {MIPS_REG_V1, W22}, {MIPS_REG_A0, W21}, + {MIPS_REG_RA, W23}, }; if (jo_->useStaticAlloc) { From 79a9018aace7501c9392b0b3f2ffdee04081f94a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 00:37:34 -0800 Subject: [PATCH 046/131] Debugger: Fix hang when viewing invalid RAM. If you end up in disassembly near invalid RAM, bad things were happening, since hitting bad RAM updates the debugger, which hits bad RAM, which... --- Windows/Debugger/CtrlDisAsmView.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 129d4a731a..fecc7de397 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -228,7 +228,8 @@ bool CtrlDisAsmView::getDisasmAddressText(u32 address, char* dest, bool abbrevia } } else { if (showData) { - sprintf(dest, "%08X %08X", address, Memory::Read_Instruction(address, true).encoding); + u32 encoding = Memory::IsValidAddress(address) ? Memory::Read_Instruction(address, true).encoding : 0; + sprintf(dest, "%08X %08X", address, encoding); } else { sprintf(dest, "%08X", address); } From 9ff812b31352724cdca6135da7ab2077dfa6ff74 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 30 Dec 2017 10:57:50 -0800 Subject: [PATCH 047/131] arm64jit: Negate in ADDI2R/SUBI2R as well. Should've done this at the same time as CMN. It's not as common, mostly catches addu calls, but it's good to have these generic for other uses. --- Common/Arm64Emitter.cpp | 8 ++++++++ Core/MIPS/ARM64/Arm64CompALU.cpp | 6 +----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 52bf53e1f7..abf9747601 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -3736,6 +3736,7 @@ void ARM64XEmitter::CMPI2R(ARM64Reg Rn, u64 imm, ARM64Reg scratch) { } bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + s64 negated = Is64Bit(Rn) ? -(s64)imm : -(s32)(u32)imm; u32 val; bool shift; if (imm == 0) { @@ -3745,12 +3746,16 @@ bool ARM64XEmitter::TryADDI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { } else if (IsImmArithmetic(imm, &val, &shift)) { ADD(Rd, Rn, val, shift); return true; + } else if (IsImmArithmetic((u64)negated, &val, &shift)) { + SUB(Rd, Rn, val, shift); + return true; } else { return false; } } bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { + s64 negated = Is64Bit(Rn) ? -(s64)imm : -(s32)(u32)imm; u32 val; bool shift; if (imm == 0) { @@ -3760,6 +3765,9 @@ bool ARM64XEmitter::TrySUBI2R(ARM64Reg Rd, ARM64Reg Rn, u64 imm) { } else if (IsImmArithmetic(imm, &val, &shift)) { SUB(Rd, Rn, val, shift); return true; + } else if (IsImmArithmetic((u64)negated, &val, &shift)) { + ADD(Rd, Rn, val, shift); + return true; } else { return false; } diff --git a/Core/MIPS/ARM64/Arm64CompALU.cpp b/Core/MIPS/ARM64/Arm64CompALU.cpp index e9400f1efa..48572f3d96 100644 --- a/Core/MIPS/ARM64/Arm64CompALU.cpp +++ b/Core/MIPS/ARM64/Arm64CompALU.cpp @@ -90,11 +90,7 @@ void Arm64Jit::Comp_IType(MIPSOpcode op) { ARM64Reg r32 = gpr.RPtr(rs); gpr.MarkDirty(r32); ARM64Reg r = EncodeRegTo64(r32); - if (simm > 0) { - ADDI2R(r, r, simm); - } else { - SUBI2R(r, r, -simm); - } + ADDI2R(r, r, simm); } else { if (simm >= 0) { CompImmLogic(rs, rt, simm, &ARM64XEmitter::ADD, &ARM64XEmitter::TryADDI2R, &EvalAdd); From 191c4ddb0d56c3ab271e875d8386a05735f8f557 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 30 Dec 2017 21:03:28 +0100 Subject: [PATCH 048/131] Add a bit of logging to try to figure out #10477 --- Common/Vulkan/VulkanContext.cpp | 10 ++++++++-- Common/Vulkan/VulkanContext.h | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 41a9880826..782c652e68 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -132,7 +132,13 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { instance_extensions_enabled_.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); } - VkApplicationInfo app_info { VK_STRUCTURE_TYPE_APPLICATION_INFO }; + // Validate that all the instance extensions we ask for are actually available. + for (auto ext : instance_extensions_enabled_) { + if (!IsInstanceExtensionAvailable(ext)) + WLOG("WARNING: Does not seem that instance extension '%s' is available. Trying to proceed anyway.", ext); + } + + VkApplicationInfo app_info{ VK_STRUCTURE_TYPE_APPLICATION_INFO }; app_info.pApplicationName = info.app_name; app_info.applicationVersion = info.app_ver; app_info.pEngineName = info.app_name; @@ -140,7 +146,7 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { app_info.engineVersion = 2; app_info.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo inst_info { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; + VkInstanceCreateInfo inst_info{ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; inst_info.flags = 0; inst_info.pApplicationInfo = &app_info; inst_info.enabledLayerCount = (uint32_t)instance_layer_names_.size(); diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index 03b9f1b21a..aeb2e94e43 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -196,6 +196,14 @@ public: const VulkanPhysicalDeviceInfo &GetDeviceInfo() const { return deviceInfo_; } const VkSurfaceCapabilitiesKHR &GetSurfaceCapabilities() const { return surfCapabilities_; } + bool IsInstanceExtensionAvailable(const char *name) const { + for (auto &iter : instance_extension_properties_) { + if (!strcmp(name, iter.extensionName)) + return true; + } + return false; + } + bool IsDeviceExtensionAvailable(const char *name) const { for (auto &iter : device_extension_properties_) { if (!strcmp(name, iter.extensionName)) From 25a9573f8b9b20a8c4d3c2fe5f265db6b9ffb62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 30 Dec 2017 21:30:31 +0100 Subject: [PATCH 049/131] Typo fix --- Common/Vulkan/VulkanLoader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common/Vulkan/VulkanLoader.cpp b/Common/Vulkan/VulkanLoader.cpp index c82c0eef95..72a3237f04 100644 --- a/Common/Vulkan/VulkanLoader.cpp +++ b/Common/Vulkan/VulkanLoader.cpp @@ -420,7 +420,7 @@ void VulkanLoadInstanceFunctions(VkInstance instance) { LOAD_INSTANCE_FUNC(instance, vkDestroySurfaceKHR); dyn_vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); - dyn_vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); + dyn_vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); WLOG("Vulkan instance functions loaded."); } From b9726245acf5a151a437d6fa89b574aba3b63011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 30 Dec 2017 21:31:43 +0100 Subject: [PATCH 050/131] Vulkan: Use VK_KHR_DEDICATED_ALLOCATION for frame buffers for minor speedup on some GPUs. Check for the debug report extension before enabling it. --- Common/Vulkan/VulkanContext.cpp | 15 +++++++++++---- Common/Vulkan/VulkanContext.h | 8 ++++++++ ext/native/thin3d/VulkanRenderManager.cpp | 11 +++++++++-- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 782c652e68..3bf537090f 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -125,11 +125,16 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { #endif if (flags_ & VULKAN_FLAG_VALIDATE) { - for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) { - instance_layer_names_.push_back(validationLayers[i]); - device_layer_names_.push_back(validationLayers[i]); + if (IsInstanceExtensionAvailable(VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) { + for (size_t i = 0; i < ARRAY_SIZE(validationLayers); i++) { + instance_layer_names_.push_back(validationLayers[i]); + device_layer_names_.push_back(validationLayers[i]); + } + instance_extensions_enabled_.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + } else { + ELOG("Validation layer extension not available - not enabling Vulkan validation."); + flags_ &= ~VULKAN_FLAG_VALIDATE; } - instance_extensions_enabled_.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); } // Validate that all the instance extensions we ask for are actually available. @@ -550,6 +555,8 @@ VkResult VulkanContext::CreateDevice() { } assert(found); + deviceExtensionsLookup_.DEDICATED_ALLOCATION = EnableDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + VkDeviceCreateInfo device_info { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; device_info.queueCreateInfoCount = 1; device_info.pQueueCreateInfos = &queue_info; diff --git a/Common/Vulkan/VulkanContext.h b/Common/Vulkan/VulkanContext.h index aeb2e94e43..6b441d1ffa 100644 --- a/Common/Vulkan/VulkanContext.h +++ b/Common/Vulkan/VulkanContext.h @@ -104,6 +104,11 @@ private: std::vector callbacks_; }; +// For fast extension-enabled checks. +struct VulkanDeviceExtensions { + bool DEDICATED_ALLOCATION; +}; + // VulkanContext manages the device and swapchain, and deferred deletion of objects. class VulkanContext { public: @@ -233,6 +238,8 @@ public: MAX_INFLIGHT_FRAMES = 3, }; + const VulkanDeviceExtensions &DeviceExtensions() { return deviceExtensionsLookup_; } + private: // A layer can expose extensions, keep track of those extensions here. struct LayerProperties { @@ -265,6 +272,7 @@ private: std::vector device_extensions_enabled_; std::vector device_extension_properties_; + VulkanDeviceExtensions deviceExtensionsLookup_{}; std::vector physical_devices_; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 920056d683..ab9bbafc22 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -38,13 +38,20 @@ void CreateImage(VulkanContext *vulkan, VkCommandBuffer cmd, VKRImage &img, int vkCreateImage(vulkan->GetDevice(), &ici, nullptr, &img.image); - // TODO: If available, use nVidia's VK_NV_dedicated_allocation for framebuffers - VkMemoryRequirements memreq; vkGetImageMemoryRequirements(vulkan->GetDevice(), img.image, &memreq); VkMemoryAllocateInfo alloc{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; alloc.allocationSize = memreq.size; + + // Hint to the driver that this allocation is image-specific. Some drivers benefit. + // We only bother supporting the KHR extension, not the old NV one. + VkMemoryDedicatedAllocateInfoKHR dedicated{ VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR }; + if (vulkan->DeviceExtensions().DEDICATED_ALLOCATION) { + alloc.pNext = &dedicated; + dedicated.image = img.image; + } + vulkan->MemoryTypeFromProperties(memreq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &alloc.memoryTypeIndex); VkResult res = vkAllocateMemory(vulkan->GetDevice(), &alloc, nullptr, &img.memory); assert(res == VK_SUCCESS); From 1e64919392d2e98f4fba0832304fc997b73013c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 30 Dec 2017 22:35:43 +0100 Subject: [PATCH 051/131] Vulkan: Create renderpass objects on demand. Just a refactoring to prepare for some later features/optimizations. --- ext/native/thin3d/VulkanQueueRunner.cpp | 80 ++++++++++++++----------- ext/native/thin3d/VulkanQueueRunner.h | 18 +++--- ext/native/thin3d/VulkanRenderManager.h | 10 ++-- ext/native/thin3d/thin3d_vulkan.cpp | 4 +- 4 files changed, 62 insertions(+), 50 deletions(-) diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index a2c5c0a631..6bed70bd37 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -7,7 +7,6 @@ void VulkanQueueRunner::CreateDeviceObjects() { ILOG("VulkanQueueRunner::CreateDeviceObjects"); InitBackbufferRenderPass(); - InitRenderpasses(); #if 0 // Just to check whether it makes sense to split some of these. drawidx is way bigger than the others... @@ -67,11 +66,12 @@ void VulkanQueueRunner::DestroyDeviceObjects() { vulkan_->Delete().QueueDeleteBuffer(readbackBuffer_); readbackBufferSize_ = 0; - for (int i = 0; i < ARRAY_SIZE(renderPasses_); i++) { - assert(renderPasses_[i] != VK_NULL_HANDLE); - vulkan_->Delete().QueueDeleteRenderPass(renderPasses_[i]); - renderPasses_[i] = VK_NULL_HANDLE; - } + renderPasses_.Iterate([&](const RPKey &rpkey, VkRenderPass rp) { + _assert_(rp != VK_NULL_HANDLE); + vulkan_->Delete().QueueDeleteRenderPass(rp); + }); + renderPasses_.Clear(); + assert(backbufferRenderPass_ != VK_NULL_HANDLE); vulkan_->Delete().QueueDeleteRenderPass(backbufferRenderPass_); backbufferRenderPass_ = VK_NULL_HANDLE; @@ -141,13 +141,30 @@ void VulkanQueueRunner::InitBackbufferRenderPass() { assert(res == VK_SUCCESS); } -void VulkanQueueRunner::InitRenderpasses() { +VkRenderPass VulkanQueueRunner::GetRenderPass(VKRRenderPassAction colorAction, VKRRenderPassAction depthAction) { + RPKey key{ colorAction, depthAction }; + auto pass = renderPasses_.Get(key); + if (pass) { + return pass; + } + // Create a bunch of render pass objects, for normal rendering with a depth buffer, // with clearing, without clearing, and dont-care for both depth/stencil and color, so 3*3=9 combos. VkAttachmentDescription attachments[2] = {}; attachments[0].format = VK_FORMAT_R8G8B8A8_UNORM; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + switch (colorAction) { + case VKRRenderPassAction::CLEAR: + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case VKRRenderPassAction::KEEP: + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case VKRRenderPassAction::DONT_CARE: + default: + attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachments[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; @@ -157,9 +174,21 @@ void VulkanQueueRunner::InitRenderpasses() { attachments[1].format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + switch (depthAction) { + case VKRRenderPassAction::CLEAR: + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case VKRRenderPassAction::KEEP: + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case VKRRenderPassAction::DONT_CARE: + attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } attachments[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; attachments[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; attachments[1].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; @@ -192,31 +221,10 @@ void VulkanQueueRunner::InitRenderpasses() { rp.pSubpasses = &subpass; rp.dependencyCount = 0; - for (int depth = 0; depth < 3; depth++) { - switch ((VKRRenderPassAction)depth) { - case VKRRenderPassAction::CLEAR: - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - break; - case VKRRenderPassAction::KEEP: - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - break; - case VKRRenderPassAction::DONT_CARE: - attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; - break; - } - for (int color = 0; color < 3; color++) { - switch ((VKRRenderPassAction)color) { - case VKRRenderPassAction::CLEAR: attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; break; - case VKRRenderPassAction::KEEP: attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; break; - case VKRRenderPassAction::DONT_CARE: attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; break; - } - int index = RPIndex((VKRRenderPassAction)color, (VKRRenderPassAction)depth); - vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &renderPasses_[index]); - } - } + VkResult res = vkCreateRenderPass(vulkan_->GetDevice(), &rp, nullptr, &pass); + _assert_(res == VK_SUCCESS); + renderPasses_.Insert(key, pass); + return pass; } void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, const std::vector &steps) { @@ -596,7 +604,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step fb->depth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } - renderPass = renderPasses_[RPIndex(step.render.color, step.render.depthStencil)]; + renderPass = GetRenderPass(step.render.color, step.render.depthStencil); if (step.render.color == VKRRenderPassAction::CLEAR) { Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor); numClearVals = 1; diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index f2bafcff40..1252849563 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -2,6 +2,7 @@ #include +#include "Common/Hashmaps.h" #include "Common/Vulkan/VulkanContext.h" #include "math/dataconv.h" #include "thin3d/DataFormat.h" @@ -87,7 +88,7 @@ enum class VKRStepType : uint8_t { READBACK_IMAGE, }; -enum class VKRRenderPassAction { +enum class VKRRenderPassAction : uint8_t { DONT_CARE, CLEAR, KEEP, @@ -144,7 +145,7 @@ struct VKRStep { class VulkanQueueRunner { public: - VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan) {} + VulkanQueueRunner(VulkanContext *vulkan) : vulkan_(vulkan), renderPasses_(16) {} void SetBackbuffer(VkFramebuffer fb, VkImage img) { backbuffer_ = fb; backbufferImage_ = img; @@ -158,9 +159,7 @@ public: VkRenderPass GetBackbufferRenderPass() const { return backbufferRenderPass_; } - VkRenderPass GetRenderPass(int i) const { - return renderPasses_[i]; - } + VkRenderPass GetRenderPass(VKRRenderPassAction color, VKRRenderPassAction depth); inline int RPIndex(VKRRenderPassAction color, VKRRenderPassAction depth) { return (int)depth * 3 + (int)color; @@ -170,7 +169,6 @@ public: private: void InitBackbufferRenderPass(); - void InitRenderpasses(); void PerformBindFramebufferAsRenderTarget(const VKRStep &pass, VkCommandBuffer cmd); void PerformRenderPass(const VKRStep &pass, VkCommandBuffer cmd); @@ -197,9 +195,15 @@ private: VkFramebuffer curFramebuffer_ = VK_NULL_HANDLE; VkRenderPass backbufferRenderPass_ = VK_NULL_HANDLE; + + struct RPKey { + VKRRenderPassAction colorAction; + VKRRenderPassAction depthStencilAction; + }; + // Renderpasses, all combinations of preserving or clearing or dont-care-ing fb contents. // TODO: Create these on demand. - VkRenderPass renderPasses_[9]{}; + DenseHashMap renderPasses_; // Readback buffer. Currently we only support synchronous readback, so we only really need one. // We size it generously. diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index c5e13130af..788f7255be 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -194,15 +194,15 @@ public: VkCommandBuffer GetInitCmd(); - VkRenderPass GetRenderPass(int pass) const { - return queueRunner_.GetRenderPass(pass); + VkRenderPass GetRenderPass(VKRRenderPassAction color, VKRRenderPassAction depthStencil) { + return queueRunner_.GetRenderPass(color, depthStencil); } - VkRenderPass GetBackbufferRenderPass() const { + VkRenderPass GetBackbufferRenderPass() { return queueRunner_.GetBackbufferRenderPass(); } - VkRenderPass GetCompatibleRenderPass() const { + VkRenderPass GetCompatibleRenderPass() { if (curRenderStep_ && curRenderStep_->render.framebuffer != nullptr) { - return queueRunner_.GetRenderPass(0); + return queueRunner_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); } else { return queueRunner_.GetBackbufferRenderPass(); } diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index f8906188a7..da389b9d99 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -462,7 +462,7 @@ public: switch (obj) { case NativeObject::FRAMEBUFFER_RENDERPASS: // Return a representative renderpass. - return (uintptr_t)renderManager_.GetRenderPass(0); + return (uintptr_t)renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); case NativeObject::BACKBUFFER_RENDERPASS: return (uintptr_t)renderManager_.GetBackbufferRenderPass(); case NativeObject::COMPATIBLE_RENDERPASS: @@ -1295,7 +1295,7 @@ private: Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) { VkCommandBuffer cmd = renderManager_.GetInitCmd(); - VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetRenderPass(0), desc.width, desc.height); + VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR), desc.width, desc.height); return new VKFramebuffer(vkrfb); } From a0f718ace1ebab045c0d94b6124d6227a0124470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 30 Dec 2017 22:40:57 +0100 Subject: [PATCH 052/131] Start separating depth and stencil renderpass actions, only internally in the Vulkan code so far. --- ext/native/thin3d/VulkanQueueRunner.cpp | 22 +++++++++++++++------- ext/native/thin3d/VulkanQueueRunner.h | 5 +++-- ext/native/thin3d/VulkanRenderManager.h | 6 +++--- ext/native/thin3d/thin3d_vulkan.cpp | 4 ++-- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index 6bed70bd37..a768d5b9ed 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -141,8 +141,8 @@ void VulkanQueueRunner::InitBackbufferRenderPass() { assert(res == VK_SUCCESS); } -VkRenderPass VulkanQueueRunner::GetRenderPass(VKRRenderPassAction colorAction, VKRRenderPassAction depthAction) { - RPKey key{ colorAction, depthAction }; +VkRenderPass VulkanQueueRunner::GetRenderPass(VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction) { + RPKey key{ colorLoadAction, depthLoadAction, stencilLoadAction }; auto pass = renderPasses_.Get(key); if (pass) { return pass; @@ -153,7 +153,7 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(VKRRenderPassAction colorAction, V VkAttachmentDescription attachments[2] = {}; attachments[0].format = VK_FORMAT_R8G8B8A8_UNORM; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; - switch (colorAction) { + switch (colorLoadAction) { case VKRRenderPassAction::CLEAR: attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; break; @@ -174,17 +174,25 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(VKRRenderPassAction colorAction, V attachments[1].format = vulkan_->GetDeviceInfo().preferredDepthStencilFormat; attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; - switch (depthAction) { + switch (depthLoadAction) { case VKRRenderPassAction::CLEAR: attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; break; case VKRRenderPassAction::KEEP: attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; break; case VKRRenderPassAction::DONT_CARE: attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + break; + } + switch (stencilLoadAction) { + case VKRRenderPassAction::CLEAR: + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + break; + case VKRRenderPassAction::KEEP: + attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + break; + case VKRRenderPassAction::DONT_CARE: attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; break; } @@ -604,7 +612,7 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step fb->depth.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } - renderPass = GetRenderPass(step.render.color, step.render.depthStencil); + renderPass = GetRenderPass(step.render.color, step.render.depthStencil, step.render.depthStencil); if (step.render.color == VKRRenderPassAction::CLEAR) { Uint8x4ToFloat4(clearVal[0].color.float32, step.render.clearColor); numClearVals = 1; diff --git a/ext/native/thin3d/VulkanQueueRunner.h b/ext/native/thin3d/VulkanQueueRunner.h index 1252849563..1049a5bb80 100644 --- a/ext/native/thin3d/VulkanQueueRunner.h +++ b/ext/native/thin3d/VulkanQueueRunner.h @@ -159,7 +159,7 @@ public: VkRenderPass GetBackbufferRenderPass() const { return backbufferRenderPass_; } - VkRenderPass GetRenderPass(VKRRenderPassAction color, VKRRenderPassAction depth); + VkRenderPass GetRenderPass(VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction); inline int RPIndex(VKRRenderPassAction color, VKRRenderPassAction depth) { return (int)depth * 3 + (int)color; @@ -198,7 +198,8 @@ private: struct RPKey { VKRRenderPassAction colorAction; - VKRRenderPassAction depthStencilAction; + VKRRenderPassAction depthAction; + VKRRenderPassAction stencilAction; }; // Renderpasses, all combinations of preserving or clearing or dont-care-ing fb contents. diff --git a/ext/native/thin3d/VulkanRenderManager.h b/ext/native/thin3d/VulkanRenderManager.h index 788f7255be..b074e3b4f9 100644 --- a/ext/native/thin3d/VulkanRenderManager.h +++ b/ext/native/thin3d/VulkanRenderManager.h @@ -194,15 +194,15 @@ public: VkCommandBuffer GetInitCmd(); - VkRenderPass GetRenderPass(VKRRenderPassAction color, VKRRenderPassAction depthStencil) { - return queueRunner_.GetRenderPass(color, depthStencil); + VkRenderPass GetRenderPass(VKRRenderPassAction colorLoadAction, VKRRenderPassAction depthLoadAction, VKRRenderPassAction stencilLoadAction) { + return queueRunner_.GetRenderPass(colorLoadAction, depthLoadAction, stencilLoadAction); } VkRenderPass GetBackbufferRenderPass() { return queueRunner_.GetBackbufferRenderPass(); } VkRenderPass GetCompatibleRenderPass() { if (curRenderStep_ && curRenderStep_->render.framebuffer != nullptr) { - return queueRunner_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); + return queueRunner_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); } else { return queueRunner_.GetBackbufferRenderPass(); } diff --git a/ext/native/thin3d/thin3d_vulkan.cpp b/ext/native/thin3d/thin3d_vulkan.cpp index da389b9d99..2dac9803fa 100644 --- a/ext/native/thin3d/thin3d_vulkan.cpp +++ b/ext/native/thin3d/thin3d_vulkan.cpp @@ -462,7 +462,7 @@ public: switch (obj) { case NativeObject::FRAMEBUFFER_RENDERPASS: // Return a representative renderpass. - return (uintptr_t)renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); + return (uintptr_t)renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR); case NativeObject::BACKBUFFER_RENDERPASS: return (uintptr_t)renderManager_.GetBackbufferRenderPass(); case NativeObject::COMPATIBLE_RENDERPASS: @@ -1295,7 +1295,7 @@ private: Framebuffer *VKContext::CreateFramebuffer(const FramebufferDesc &desc) { VkCommandBuffer cmd = renderManager_.GetInitCmd(); - VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR), desc.width, desc.height); + VKRFramebuffer *vkrfb = new VKRFramebuffer(vulkan_, cmd, renderManager_.GetRenderPass(VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR, VKRRenderPassAction::CLEAR), desc.width, desc.height); return new VKFramebuffer(vkrfb); } From b5e5d72a0a4327dd8a79d3fd7e703092323eb77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 30 Dec 2017 22:52:22 +0100 Subject: [PATCH 053/131] Complete the separation of depth and stencil renderpass flags --- GPU/Common/FramebufferCommon.cpp | 36 +++++++++++++-------------- GPU/D3D11/FramebufferManagerD3D11.cpp | 12 ++++----- GPU/D3D11/StencilBufferD3D11.cpp | 2 +- GPU/D3D11/TextureCacheD3D11.cpp | 2 +- GPU/Directx9/FramebufferDX9.cpp | 6 ++--- GPU/Directx9/StencilBufferDX9.cpp | 2 +- GPU/Directx9/TextureCacheDX9.cpp | 2 +- GPU/GLES/FramebufferManagerGLES.cpp | 18 +++++++------- GPU/GLES/StencilBufferGLES.cpp | 4 +-- GPU/GLES/TextureCacheGLES.cpp | 2 +- GPU/Software/SoftGpu.cpp | 4 +-- GPU/Vulkan/FramebufferVulkan.cpp | 8 +++--- GPU/Vulkan/StencilBufferVulkan.cpp | 4 +-- GPU/Vulkan/TextureCacheVulkan.cpp | 2 +- ext/native/thin3d/thin3d.h | 1 + ext/native/thin3d/thin3d_d3d11.cpp | 11 ++++++-- ext/native/thin3d/thin3d_d3d9.cpp | 5 +++- ext/native/thin3d/thin3d_gl.cpp | 9 +++++-- ext/native/ui/ui_screen.cpp | 2 +- 19 files changed, 74 insertions(+), 58 deletions(-) diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index 9e0081ec8e..94072a85b2 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -209,7 +209,7 @@ void FramebufferManagerCommon::SetNumExtraFBOs(int num) { } currentRenderVfb_ = 0; if (num != 0) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } // Heuristics to figure out the size of FBO to create. @@ -607,12 +607,12 @@ void FramebufferManagerCommon::NotifyRenderFramebufferSwitched(VirtualFramebuffe // FBO in a frame. This means that some games won't be able to avoid the on-some-GPUs // performance-crushing framebuffer reloads from RAM, but we'll have to live with that. if (vfb->last_frame_render != gpuStats.numFlips) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } else { // This should only happen very briefly when toggling useBufferedRendering_. @@ -721,7 +721,7 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height, u1, v1); if (useBufferedRendering_ && vfb && vfb->fbo) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight); draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight); } else { @@ -849,7 +849,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { DEBUG_LOG(FRAMEBUF, "Display disabled, displaying only black"); // No framebuffer to display! Clear to black. if (useBufferedRendering_) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } return; } @@ -914,7 +914,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { if (!vfb) { if (useBufferedRendering_) { // Bind and clear the backbuffer. This should be the first time during the frame that it's bound. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } // Just a pointer to plain memory to draw. We should create a framebuffer, then draw to it. SetViewport2D(0, 0, pixelWidth_, pixelHeight_); @@ -927,7 +927,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { // No framebuffer to display! Clear to black. if (useBufferedRendering_) { // Bind and clear the backbuffer. This should be the first time during the frame that it's bound. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } return; } @@ -964,7 +964,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { float v1 = (272.0f + offsetY) / (float)vfb->bufferHeight; if (!usePostShader_) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); DrawTextureFlags flags = g_Config.iBufFilter == SCALE_LINEAR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; @@ -988,7 +988,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { } } else if (usePostShader_ && extraFBOs_.size() == 1 && !postShaderAtOutputResolution_) { // An additional pass, post-processing shader to the extra FBO. - draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0); int fbo_w, fbo_h; draw_->GetFramebufferDimensions(extraFBOs_[0], &fbo_w, &fbo_h); @@ -1001,7 +1001,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { DrawTextureFlags flags = g_Config.iBufFilter == SCALE_LINEAR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; DrawActiveTexture(0, 0, fbo_w, fbo_h, fbo_w, fbo_h, 0.0f, 0.0f, 1.0f, 1.0f, ROTATION_LOCKED_HORIZONTAL, flags); - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); SetViewport2D(0, 0, pixelWidth_, pixelHeight_); // Use the extra FBO, with applied post-processing shader, as a texture. @@ -1034,12 +1034,12 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { /* if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { - draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(extraFBOs_[0], { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT }; glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments); }*/ } else { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); draw_->BindFramebufferAsTexture(vfb->fbo, 0, Draw::FB_COLOR_BIT, 0); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); // We are doing the DrawActiveTexture call directly to the backbuffer here. Hence, we must @@ -1190,17 +1190,17 @@ void FramebufferManagerCommon::ResizeFramebufFBO(VirtualFramebuffer *vfb, int w, if (old.fbo) { INFO_LOG(FRAMEBUF, "Resizing FBO for %08x : %d x %d x %d", vfb->fb_address, w, h, vfb->format); if (vfb->fbo) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); if (!skipCopy && !g_Config.bDisableSlowFramebufEffects) { BlitFramebuffer(vfb, 0, 0, &old, 0, 0, std::min((u16)oldWidth, std::min(vfb->bufferWidth, vfb->width)), std::min((u16)oldHeight, std::min(vfb->height, vfb->bufferHeight)), 0); } } fbosToDelete_.push_back(old.fbo); if (needGLESRebinds_) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } if (!vfb->fbo) { @@ -2115,10 +2115,10 @@ void FramebufferManagerCommon::DownloadFramebufferForClut(u32 fb_address, u32 lo void FramebufferManagerCommon::RebindFramebuffer() { if (currentRenderVfb_ && currentRenderVfb_->fbo) { - draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } else { // Should this even happen? It could while debugging, but maybe we can just skip binding at all. - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); } diff --git a/GPU/D3D11/FramebufferManagerD3D11.cpp b/GPU/D3D11/FramebufferManagerD3D11.cpp index 4d29240d2a..6da4b477c1 100644 --- a/GPU/D3D11/FramebufferManagerD3D11.cpp +++ b/GPU/D3D11/FramebufferManagerD3D11.cpp @@ -461,10 +461,10 @@ void FramebufferManagerD3D11::BindPostShader(const PostShaderUniforms &uniforms) void FramebufferManagerD3D11::RebindFramebuffer() { if (currentRenderVfb_ && currentRenderVfb_->fbo) { - draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(currentRenderVfb_->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } else { // Should this even happen? - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } @@ -482,7 +482,7 @@ void FramebufferManagerD3D11::ReformatFramebufferFrom(VirtualFramebuffer *vfb, G // and blit with a shader to that, then replace the FBO on vfb. Stencil would still be complex // to exactly reproduce in 4444 and 8888 formats. if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); // TODO: There's no way this does anything useful :( context_->OMSetDepthStencilState(stockD3D11.depthDisabledStencilWrite, 0xFF); @@ -591,7 +591,7 @@ bool FramebufferManagerD3D11::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb) return false; } - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); return true; } @@ -634,7 +634,7 @@ void FramebufferManagerD3D11::SimpleBlit( // Unbind the texture first to avoid the D3D11 hazard check (can't set render target to things bound as textures and vice versa, not even temporarily). draw_->BindTexture(0, nullptr); - draw_->BindFramebufferAsRenderTarget(dest, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(dest, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); draw_->BindFramebufferAsTexture(src, 0, Draw::FB_COLOR_BIT, 0); Bind2DShader(); @@ -657,7 +657,7 @@ void FramebufferManagerD3D11::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. if (useBufferedRendering_) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } return; } diff --git a/GPU/D3D11/StencilBufferD3D11.cpp b/GPU/D3D11/StencilBufferD3D11.cpp index 9a8a44a888..a64c25e8ee 100644 --- a/GPU/D3D11/StencilBufferD3D11.cpp +++ b/GPU/D3D11/StencilBufferD3D11.cpp @@ -161,7 +161,7 @@ bool FramebufferManagerD3D11::NotifyStencilUpload(u32 addr, int size, bool skipZ float v1 = 1.0f; MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { // something is wrong... } diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index b3c04b0c46..425d8b738b 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -398,7 +398,7 @@ void TextureCacheD3D11::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFra context_->PSSetShaderResources(1, 1, &clutTexture); framebufferManagerD3D11_->BindFramebufferAsColorTexture(0, framebuffer, BINDFBCOLOR_SKIP_COPY); context_->PSSetSamplers(0, 1, &stockD3D11.samplerPoint2DWrap); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderApply.Shade(); framebufferManagerD3D11_->RebindFramebuffer(); diff --git a/GPU/Directx9/FramebufferDX9.cpp b/GPU/Directx9/FramebufferDX9.cpp index b80e0d41c0..fa0235a2c5 100644 --- a/GPU/Directx9/FramebufferDX9.cpp +++ b/GPU/Directx9/FramebufferDX9.cpp @@ -319,7 +319,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { return; } - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); // Technically, we should at this point re-interpret the bytes of the old format to the new. // That might get tricky, and could cause unnecessary slowness in some games. @@ -478,7 +478,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { return false; } - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); return true; } @@ -490,7 +490,7 @@ static const D3DVERTEXELEMENT9 g_FramebufferVertexElements[] = { if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if we recently switched from non-buffered. if (useBufferedRendering_) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); return; } diff --git a/GPU/Directx9/StencilBufferDX9.cpp b/GPU/Directx9/StencilBufferDX9.cpp index 8d8e91b610..e83f2a05dc 100644 --- a/GPU/Directx9/StencilBufferDX9.cpp +++ b/GPU/Directx9/StencilBufferDX9.cpp @@ -191,7 +191,7 @@ bool FramebufferManagerDX9::NotifyStencilUpload(u32 addr, int size, bool skipZer u16 h = dstBuffer->renderHeight; if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } D3DVIEWPORT9 vp{ 0, 0, w, h, 0.0f, 1.0f }; device_->SetViewport(&vp); diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 83173039ac..123eebd0ff 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -425,7 +425,7 @@ void TextureCacheDX9::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFrame LPDIRECT3DTEXTURE9 clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); Draw::Framebuffer *depalFBO = framebufferManagerDX9_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderManager_->DirtyLastShader(); float xoff = -0.5f / framebuffer->renderWidth; diff --git a/GPU/GLES/FramebufferManagerGLES.cpp b/GPU/GLES/FramebufferManagerGLES.cpp index 5faef65a64..20eb4aaf8c 100644 --- a/GPU/GLES/FramebufferManagerGLES.cpp +++ b/GPU/GLES/FramebufferManagerGLES.cpp @@ -504,9 +504,9 @@ void FramebufferManagerGLES::ReformatFramebufferFrom(VirtualFramebuffer *vfb, GE // to exactly reproduce in 4444 and 8888 formats. if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } RebindFramebuffer(); @@ -640,11 +640,11 @@ void FramebufferManagerGLES::UpdateDownloadTempBuffer(VirtualFramebuffer *nvfb) // Discard the previous contents of this buffer where possible. if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_STENCIL_ATTACHMENT, GL_DEPTH_ATTACHMENT }; glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments); } else if (gl_extensions.IsGLES) { - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } CHECK_GL_ERROR_IF_DEBUG(); } @@ -653,7 +653,7 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. if (useBufferedRendering_) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); return; } @@ -708,7 +708,7 @@ void FramebufferManagerGLES::BlitFramebuffer(VirtualFramebuffer *dst, int dstX, if (useBlit) { draw_->BlitFramebuffer(src->fbo, srcX1, srcY1, srcX2, srcY2, dst->fbo, dstX1, dstY1, dstX2, dstY2, Draw::FB_COLOR_BIT, Draw::FB_BLIT_NEAREST); } else { - draw_->BindFramebufferAsRenderTarget(dst->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(dst->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); draw_->BindFramebufferAsTexture(src->fbo, 0, Draw::FB_COLOR_BIT, 0); // Make sure our 2D drawing program is ready. Compiles only if not already compiled. @@ -980,7 +980,7 @@ void FramebufferManagerGLES::PackFramebufferSync_(VirtualFramebuffer *vfb, int x if (gl_extensions.GLES3 && glInvalidateFramebuffer != nullptr) { #ifdef USING_GLES2 // GLES3 doesn't support using GL_READ_FRAMEBUFFER here. - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); const GLenum target = GL_FRAMEBUFFER; #else const GLenum target = GL_READ_FRAMEBUFFER; @@ -1044,11 +1044,11 @@ void FramebufferManagerGLES::EndFrame() { continue; } - draw_->BindFramebufferAsRenderTarget(temp.second.fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(temp.second.fbo, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); GLenum attachments[3] = { GL_COLOR_ATTACHMENT0, GL_STENCIL_ATTACHMENT, GL_DEPTH_ATTACHMENT }; glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, attachments); } - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP , Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP , Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } CHECK_GL_ERROR_IF_DEBUG(); } diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index de04a7c207..ee04042431 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -160,9 +160,9 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe Draw::Framebuffer *blitFBO = nullptr; if (useBlit) { blitFBO = GetTempFBO(w, h, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); } else if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } glViewport(0, 0, w, h); gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index f7c754e514..f9daf9faef 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -493,7 +493,7 @@ void TextureCacheGLES::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFram const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); GLuint clutTexture = depalShaderCache_->GetClutTexture(clutFormat, clutHash_, clutBuf_); Draw::Framebuffer *depalFBO = framebufferManagerGL_->GetTempFBO(framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); shaderManager_->DirtyLastShader(); TextureShaderApplier shaderApply(depal, framebuffer->bufferWidth, framebuffer->bufferHeight, framebuffer->renderWidth, framebuffer->renderHeight); diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index d449c2d357..bd14ab1e2f 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -213,7 +213,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { u1 = 1.0f; } if (!hasImage) { - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); return; } @@ -247,7 +247,7 @@ void SoftGPU::CopyToCurrentFboFromDisplayRam(int srcwidth, int srcheight) { if (GetGPUBackend() == GPUBackend::VULKAN) { std::swap(v0, v1); } - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::CLEAR, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); Draw::Viewport viewport = { 0.0f, 0.0f, dstwidth, dstheight, 0.0f, 1.0f }; draw_->SetViewports(1, &viewport); draw_->SetScissorRect(0, 0, dstwidth, dstheight); diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index f174ffeb31..607768d71f 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -394,9 +394,9 @@ void FramebufferManagerVulkan::ReformatFramebufferFrom(VirtualFramebuffer *vfb, // to exactly reproduce in 4444 and 8888 formats. if (old == GE_FORMAT_565) { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); } } @@ -467,7 +467,7 @@ bool FramebufferManagerVulkan::CreateDownloadTempBuffer(VirtualFramebuffer *nvfb return false; } - draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(nvfb->fbo, { Draw::RPAction::CLEAR, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); return true; } @@ -479,7 +479,7 @@ void FramebufferManagerVulkan::BlitFramebuffer(VirtualFramebuffer *dst, int dstX if (!dst->fbo || !src->fbo || !useBufferedRendering_) { // This can happen if they recently switched from non-buffered. if (useBufferedRendering_) - draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP }); + draw_->BindFramebufferAsRenderTarget(nullptr, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }); return; } diff --git a/GPU/Vulkan/StencilBufferVulkan.cpp b/GPU/Vulkan/StencilBufferVulkan.cpp index 29e379d9bc..6cab9e59a9 100644 --- a/GPU/Vulkan/StencilBufferVulkan.cpp +++ b/GPU/Vulkan/StencilBufferVulkan.cpp @@ -123,7 +123,7 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip } // TODO: Find a nice way to clear alpha here too. - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); return true; } @@ -137,7 +137,7 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip float v1 = 1.0f; MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); } else { // something is wrong... } diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 1b7d774a61..4b844b192b 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -346,7 +346,7 @@ void TextureCacheVulkan::ApplyTextureFramebuffer(TexCacheEntry *entry, VirtualFr Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO( framebuffer->renderWidth, framebuffer->renderHeight, Draw::FBO_8888); - draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); Vulkan2D::Vertex verts[4] = { { -1, -1, 0.0f, 0, 0 }, diff --git a/ext/native/thin3d/thin3d.h b/ext/native/thin3d/thin3d.h index 916cc97f7e..b401a6268f 100644 --- a/ext/native/thin3d/thin3d.h +++ b/ext/native/thin3d/thin3d.h @@ -532,6 +532,7 @@ enum class RPAction { struct RenderPassInfo { RPAction color; RPAction depth; + RPAction stencil; uint32_t clearColor; float clearDepth; uint8_t clearStencil; diff --git a/ext/native/thin3d/thin3d_d3d11.cpp b/ext/native/thin3d/thin3d_d3d11.cpp index bd42955300..2b2c761279 100644 --- a/ext/native/thin3d/thin3d_d3d11.cpp +++ b/ext/native/thin3d/thin3d_d3d11.cpp @@ -1460,8 +1460,15 @@ void D3D11DrawContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Ren Uint8x4ToFloat4(cv, rp.clearColor); context_->ClearRenderTargetView(curRenderTargetView_, cv); } - if (rp.depth == RPAction::CLEAR && curDepthStencilView_) { - context_->ClearDepthStencilView(curDepthStencilView_, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, rp.clearDepth, rp.clearStencil); + int mask = 0; + if (rp.depth == RPAction::CLEAR) { + mask |= D3D11_CLEAR_DEPTH; + } + if (rp.stencil == RPAction::CLEAR) { + mask |= D3D11_CLEAR_STENCIL; + } + if (mask && curDepthStencilView_) { + context_->ClearDepthStencilView(curDepthStencilView_, mask, rp.clearDepth, rp.clearStencil); } } diff --git a/ext/native/thin3d/thin3d_d3d9.cpp b/ext/native/thin3d/thin3d_d3d9.cpp index f4c0ec8302..627031991d 100644 --- a/ext/native/thin3d/thin3d_d3d9.cpp +++ b/ext/native/thin3d/thin3d_d3d9.cpp @@ -1079,7 +1079,10 @@ void D3D9Context::BindFramebufferAsRenderTarget(Framebuffer *fbo, const RenderPa clearFlags |= D3DCLEAR_TARGET; } if (rp.depth == RPAction::CLEAR) { - clearFlags |= D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL; + clearFlags |= D3DCLEAR_ZBUFFER; + } + if (rp.stencil == RPAction::CLEAR) { + clearFlags |= D3DCLEAR_STENCIL; } if (clearFlags) { dxstate.scissorTest.force(false); diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index a01810850b..95f9f6532a 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -1661,9 +1661,12 @@ void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Render #else glClearDepth(rp.clearDepth); #endif - glClearStencil(rp.clearStencil); - clearFlags |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + clearFlags |= GL_DEPTH_BUFFER_BIT; glstate.depthWrite.force(GL_TRUE); + } + if (rp.stencil == RPAction::CLEAR) { + glClearStencil(rp.clearStencil); + clearFlags |= GL_STENCIL_BUFFER_BIT; glstate.stencilFunc.force(GL_ALWAYS, 0, 0); glstate.stencilMask.force(0xFF); } @@ -1677,6 +1680,8 @@ void OpenGLContext::BindFramebufferAsRenderTarget(Framebuffer *fbo, const Render } if (rp.depth == RPAction::CLEAR) { glstate.depthWrite.restore(); + } + if (rp.stencil == RPAction::CLEAR) { glstate.stencilFunc.restore(); glstate.stencilMask.restore(); } diff --git a/ext/native/ui/ui_screen.cpp b/ext/native/ui/ui_screen.cpp index 6d8c81e4e1..26e12b8b4f 100644 --- a/ext/native/ui/ui_screen.cpp +++ b/ext/native/ui/ui_screen.cpp @@ -69,7 +69,7 @@ void UIScreen::preRender() { } draw->BeginFrame(); // Bind and clear the back buffer - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); + draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); Draw::Viewport viewport; viewport.TopLeftX = 0; From cc0327137baf7fd0f27e1c334f8f3a3a0cd0acdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 31 Dec 2017 00:07:02 +0100 Subject: [PATCH 054/131] Oops, forgot these. --- UI/EmuScreen.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index c5d60485ab..e442743a9a 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -1022,9 +1022,9 @@ void EmuScreen::preRender() { if ((!useBufferedRendering && !g_Config.bSoftwareRendering) || Core_IsStepping()) { // We need to clear here already so that drawing during the frame is done on a clean slate. if (Core_IsStepping() && gpuStats.numFlips != 0) { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE }); + draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE, RPAction::DONT_CARE }); } else { - draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); + draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }); } Viewport viewport; @@ -1057,7 +1057,7 @@ void EmuScreen::render() { // It's possible this might be set outside PSP_RunLoopFor(). // In this case, we need to double check it here. checkPowerDown(); - thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR }); + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }); renderUI(); return; } @@ -1092,12 +1092,12 @@ void EmuScreen::render() { coreState = CORE_RUNNING; } else if (coreState == CORE_STEPPING) { // If we're stepping, it's convenient not to clear the screen. - thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE }); + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE, RPAction::DONT_CARE }); } else { // Didn't actually reach the end of the frame, ran out of the blockTicks cycles. // In this case we need to bind and wipe the backbuffer, at least. // It's possible we never ended up outputted anything - make sure we have the backbuffer cleared - thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR }); + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }); } checkPowerDown(); From b9d0bf9842f77f9eb808c9fc17685805bcc199b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 31 Dec 2017 00:10:55 +0100 Subject: [PATCH 055/131] Remove outdated comment. --- ext/native/thin3d/VulkanQueueRunner.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index a768d5b9ed..e693dc834a 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -148,8 +148,6 @@ VkRenderPass VulkanQueueRunner::GetRenderPass(VKRRenderPassAction colorLoadActio return pass; } - // Create a bunch of render pass objects, for normal rendering with a depth buffer, - // with clearing, without clearing, and dont-care for both depth/stencil and color, so 3*3=9 combos. VkAttachmentDescription attachments[2] = {}; attachments[0].format = VK_FORMAT_R8G8B8A8_UNORM; attachments[0].samples = VK_SAMPLE_COUNT_1_BIT; From d565e23f4c58b24a8f29e5d94582e055ce3d69c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 31 Dec 2017 01:06:31 +0100 Subject: [PATCH 056/131] Stencil copy: Avoid clearing the depth buffer. --- GPU/D3D11/StencilBufferD3D11.cpp | 2 +- GPU/Directx9/StencilBufferDX9.cpp | 2 +- GPU/GLES/StencilBufferGLES.cpp | 2 +- GPU/Vulkan/StencilBufferVulkan.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/GPU/D3D11/StencilBufferD3D11.cpp b/GPU/D3D11/StencilBufferD3D11.cpp index a64c25e8ee..f489f5838a 100644 --- a/GPU/D3D11/StencilBufferD3D11.cpp +++ b/GPU/D3D11/StencilBufferD3D11.cpp @@ -161,7 +161,7 @@ bool FramebufferManagerD3D11::NotifyStencilUpload(u32 addr, int size, bool skipZ float v1 = 1.0f; MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } else { // something is wrong... } diff --git a/GPU/Directx9/StencilBufferDX9.cpp b/GPU/Directx9/StencilBufferDX9.cpp index e83f2a05dc..95f7b42718 100644 --- a/GPU/Directx9/StencilBufferDX9.cpp +++ b/GPU/Directx9/StencilBufferDX9.cpp @@ -191,7 +191,7 @@ bool FramebufferManagerDX9::NotifyStencilUpload(u32 addr, int size, bool skipZer u16 h = dstBuffer->renderHeight; if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } D3DVIEWPORT9 vp{ 0, 0, w, h, 0.0f, 1.0f }; device_->SetViewport(&vp); diff --git a/GPU/GLES/StencilBufferGLES.cpp b/GPU/GLES/StencilBufferGLES.cpp index ee04042431..01dfcf254e 100644 --- a/GPU/GLES/StencilBufferGLES.cpp +++ b/GPU/GLES/StencilBufferGLES.cpp @@ -162,7 +162,7 @@ bool FramebufferManagerGLES::NotifyStencilUpload(u32 addr, int size, bool skipZe blitFBO = GetTempFBO(w, h, Draw::FBO_8888); draw_->BindFramebufferAsRenderTarget(blitFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }); } else if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } glViewport(0, 0, w, h); gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); diff --git a/GPU/Vulkan/StencilBufferVulkan.cpp b/GPU/Vulkan/StencilBufferVulkan.cpp index 6cab9e59a9..8ee4666583 100644 --- a/GPU/Vulkan/StencilBufferVulkan.cpp +++ b/GPU/Vulkan/StencilBufferVulkan.cpp @@ -123,7 +123,7 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip } // TODO: Find a nice way to clear alpha here too. - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); return true; } @@ -137,7 +137,7 @@ bool FramebufferManagerVulkan::NotifyStencilUpload(u32 addr, int size, bool skip float v1 = 1.0f; MakePixelTexture(src, dstBuffer->format, dstBuffer->fb_stride, dstBuffer->bufferWidth, dstBuffer->bufferHeight, u1, v1); if (dstBuffer->fbo) { - draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::CLEAR, Draw::RPAction::CLEAR }); + draw_->BindFramebufferAsRenderTarget(dstBuffer->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::CLEAR }); } else { // something is wrong... } From 2a3c4408d2047e872e3c13f38027a801633da003 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 20:14:07 -0800 Subject: [PATCH 057/131] Fix build on iOS (only iOS 9 and up support thread_local) --- ext/native/profiler/profiler.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/native/profiler/profiler.cpp b/ext/native/profiler/profiler.cpp index 35e270b477..811b78d7bf 100644 --- a/ext/native/profiler/profiler.cpp +++ b/ext/native/profiler/profiler.cpp @@ -15,7 +15,12 @@ #define MAX_CATEGORIES 64 // Can be any number, represents max profiled names. #define MAX_DEPTH 16 // Can be any number, represents max nesting depth of profiled names. +#if PPSSPP_PLATFORM(IOS) && defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0 +// iOS did not support C++ thread_local before iOS 9 +#define MAX_THREADS 1 // Can be any number, represents concurrent threads calling the profiler. +#else #define MAX_THREADS 4 // Can be any number, represents concurrent threads calling the profiler. +#endif #define HISTORY_SIZE 128 // Must be power of 2 #ifndef _DEBUG From bcd19ee520fd133d4b3a1f3a6432db0909a96236 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 20:16:00 -0800 Subject: [PATCH 058/131] fix cmake when building for iOS in a path with spaces --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b85ea12643..c1f97ff0e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1891,8 +1891,8 @@ if(IOS) set(APP_DIR_NAME "$") endif() add_custom_command(TARGET PPSSPP POST_BUILD - COMMAND mkdir -p ${APP_DIR_NAME} - COMMAND tar -c -C ${CMAKE_CURRENT_BINARY_DIR} --exclude .DS_Store --exclude .git assets *.png | tar -x -C ${APP_DIR_NAME} + COMMAND mkdir -p \"${APP_DIR_NAME}\" + COMMAND tar -c -C ${CMAKE_CURRENT_BINARY_DIR} --exclude .DS_Store --exclude .git assets *.png | tar -x -C \"${APP_DIR_NAME}\" ) set_target_properties(${TargetBin} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/PPSSPP-Info.plist" From 6a9a511337023c989f4837201d25718d5c7906f0 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 22:44:41 -0800 Subject: [PATCH 059/131] add an include for PPSSPP_PLATFORM --- ext/native/profiler/profiler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/native/profiler/profiler.cpp b/ext/native/profiler/profiler.cpp index 811b78d7bf..bf037a9510 100644 --- a/ext/native/profiler/profiler.cpp +++ b/ext/native/profiler/profiler.cpp @@ -11,6 +11,7 @@ #include "base/logging.h" #include "base/timeutil.h" #include "gfx_es2/draw_buffer.h" +#include "ppsspp_config.h" #include "profiler/profiler.h" #define MAX_CATEGORIES 64 // Can be any number, represents max profiled names. From d8b3f4af337575a74853c252e42d4b3289eb7c70 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 20:17:29 -0800 Subject: [PATCH 060/131] Handle iOS audio session interruptions by reinitializing audio --- Core/System.cpp | 10 ++++-- Core/System.h | 1 + ios/AppDelegate.mm | 89 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/Core/System.cpp b/Core/System.cpp index 0c274f58f2..1545174a6c 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -134,6 +134,13 @@ void Audio_Init() { } } +void Audio_Shutdown() { + if (audioInitialized) { + audioInitialized = false; + host->ShutdownSound(); + } +} + bool IsOnSeparateCPUThread() { if (cpuThread != nullptr) { return cpuThreadID == std::this_thread::get_id(); @@ -290,8 +297,7 @@ void CPU_Shutdown() { __KernelShutdown(); HLEShutdown(); if (coreParameter.enableSound) { - host->ShutdownSound(); - audioInitialized = false; // deleted in ShutdownSound + Audio_Shutdown(); } pspFileSystem.Shutdown(); mipsr4k.Shutdown(); diff --git a/Core/System.h b/Core/System.h index 26d1d6e19b..9cc4cfdc89 100644 --- a/Core/System.h +++ b/Core/System.h @@ -77,6 +77,7 @@ void PSP_RunLoopFor(int cycles); void Core_UpdateDebugStats(bool collectStats); void Audio_Init(); +void Audio_Shutdown(); bool IsOnSeparateCPUThread(); bool IsAudioInitialised(); diff --git a/ios/AppDelegate.mm b/ios/AppDelegate.mm index 7926aa5f8e..5ae6e18003 100644 --- a/ios/AppDelegate.mm +++ b/ios/AppDelegate.mm @@ -1,21 +1,108 @@ #import "AppDelegate.h" #import "ViewController.h" #import "base/NativeApp.h" +#import "Core/System.h" +#import "Core/Config.h" +#import "Common/Log.h" + +#import @implementation AppDelegate + +// This will be called when the user receives and dismisses a phone call +// or other interruption to the audio session +// Registered in application:didFinishLaunchingWithOptions: +// for AVAudioSessionInterruptionNotification +-(void) handleAudioSessionInterruption:(NSNotification *)notification { + NSNumber *interruptionType = notification.userInfo[AVAudioSessionInterruptionTypeKey]; + + // Sanity check in case it's somehow not an NSNumber + if (![interruptionType respondsToSelector:@selector(unsignedIntegerValue)]) { + return; // Lets not crash + } + + switch ([interruptionType unsignedIntegerValue]) { + case AVAudioSessionInterruptionTypeBegan: + INFO_LOG(SYSTEM, "ios audio session interruption beginning"); + if (g_Config.bEnableSound) { + Audio_Shutdown(); + } + break; + + case AVAudioSessionInterruptionTypeEnded: + INFO_LOG(SYSTEM, "ios audio session interruption ending"); + if (g_Config.bEnableSound) { + /* + * Only try to reinit audio if in the foreground, otherwise + * it may fail. Instead, trust that applicationDidBecomeActive + * will do it later. + */ + if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { + Audio_Init(); + } + } + break; + + default: + break; + }; +} + +// This will be called when the iOS's shared media process was reset +// Registered in application:didFinishLaunchingWithOptions: +// for AVAudioSessionMediaServicesWereResetNotification +-(void) handleMediaServicesWereReset:(NSNotification *)notification { + INFO_LOG(SYSTEM, "ios media services were reset - reinitializing audio"); + + /* + When media services were reset, Apple recommends: + 1) Dispose of orphaned audio objects (such as players, recorders, + converters, or audio queues) and create new ones + 2) Reset any internal audio states being tracked, including all + properties of AVAudioSession + 3) When appropriate, reactivate the AVAudioSession instance using the + setActive:error: method + We accomplish this by shutting down and reinitializing audio + */ + + if (g_Config.bEnableSound) { + Audio_Shutdown(); + Audio_Init(); + } +} + -(BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.viewController = [[ViewController alloc] init]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioSessionInterruption:) name:AVAudioSessionInterruptionNotification object:[AVAudioSession sharedInstance]]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMediaServicesWereReset:) name:AVAudioSessionMediaServicesWereResetNotification object:nil]; + self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; + return YES; } +-(void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + -(void) applicationWillResignActive:(UIApplication *)application { + if (g_Config.bEnableSound) { + Audio_Shutdown(); + } + NativeMessageReceived("lost_focus", ""); } -(void) applicationDidBecomeActive:(UIApplication *)application { + if (g_Config.bEnableSound) { + Audio_Init(); + } + NativeMessageReceived("got_focus", ""); } -@end \ No newline at end of file + +@end From 4cec3d1bf316f5507c3857809f8bf46a8a60de28 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 23:26:52 -0800 Subject: [PATCH 061/131] Add AVFoundation to linked frameworks on iOS --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1f97ff0e8..a63854d225 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -681,7 +681,7 @@ elseif(IOS) ios/iCade/iCadeReaderView.h ios/iCade/iCadeReaderView.m ios/iCade/iCadeState.h) - set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL") + set(nativeExtraLibs ${nativeExtraLibs} "-framework Foundation -framework AudioToolbox -framework CoreGraphics -framework QuartzCore -framework UIKit -framework GLKit -framework OpenAL -framework AVFoundation") if(EXISTS "${CMAKE_IOS_SDK_ROOT}/System/Library/Frameworks/GameController.framework") set(nativeExtraLibs ${nativeExtraLibs} "-weak_framework GameController") endif() From ca5758f80ba1b899c86e5e79bca89b0cdf974894 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 17:56:23 -0800 Subject: [PATCH 062/131] Change iOSCoreAudio from cpp to mm. Explicitly activate the audio session. Always set the same mDataByteSize. --- CMakeLists.txt | 2 +- ios/iOSCoreAudio.cpp | 148 -------------------------------------- ios/iOSCoreAudio.mm | 168 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 149 deletions(-) delete mode 100644 ios/iOSCoreAudio.cpp create mode 100644 ios/iOSCoreAudio.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index a63854d225..e5946a516b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -672,7 +672,7 @@ elseif(IOS) ios/AppDelegate.h ios/ViewController.mm ios/ViewController.h - ios/iOSCoreAudio.cpp + ios/iOSCoreAudio.mm ios/iOSCoreAudio.h ios/PPSSPPUIApplication.h ios/PPSSPPUIApplication.mm diff --git a/ios/iOSCoreAudio.cpp b/ios/iOSCoreAudio.cpp deleted file mode 100644 index 507b1d320e..0000000000 --- a/ios/iOSCoreAudio.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright (c) 2012- PPSSPP Project. - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2.0 or later versions. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License 2.0 for more details. - -// A copy of the GPL 2.0 should have been included with the program. -// If not, see http://www.gnu.org/licenses/ - -// Official git repository and contact information can be found at -// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. - -// This code implements the emulated audio using CoreAudio for iOS -// Originally written by jtraynham - -#include "iOSCoreAudio.h" -#include - -#define SAMPLE_RATE 44100 - -#define STREAM_MAX_FRAME_COUNT 2048 -static short stream[STREAM_MAX_FRAME_COUNT * 2 * 2]; // frames * sample size * number of channels - -AudioComponentInstance audioInstance = nil; - -int NativeMix(short *audio, int num_samples); - -OSStatus iOSCoreAudioCallback(void *inRefCon, - AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, - UInt32 inBusNumber, - UInt32 inNumberFrames, - AudioBufferList *ioData) -{ - // see if we have any sound to play - UInt32 frames = (inNumberFrames > STREAM_MAX_FRAME_COUNT ? STREAM_MAX_FRAME_COUNT : inNumberFrames); - UInt32 framesReady = NativeMix(stream, frames); - if (framesReady == 0) { - // oops, we don't currently have any sound, so return silence - *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence; - return noErr; - } - - // grab the output buffer and copy data into it - AudioSampleType *output = (AudioSampleType *)ioData->mBuffers[0].mData; - UInt32 bytesReady = framesReady * sizeof(short) * 2; - memcpy(output, stream, bytesReady); - // make sure and tell it how much audio data is there - ioData->mBuffers[0].mDataByteSize = bytesReady; - - return noErr; -} - -void iOSCoreAudioInit() -{ - if (!audioInstance) { - OSErr err; - - // first, grab the default output - AudioComponentDescription defaultOutputDescription; - defaultOutputDescription.componentType = kAudioUnitType_Output; - defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO; - defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; - defaultOutputDescription.componentFlags = 0; - defaultOutputDescription.componentFlagsMask = 0; - AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); - - // create our instance - err = AudioComponentInstanceNew(defaultOutput, &audioInstance); - if (err != noErr) { - audioInstance = nil; - return; - } - - // create our callback so we can give it the audio data - AURenderCallbackStruct input; - input.inputProc = iOSCoreAudioCallback; - input.inputProcRefCon = NULL; - err = AudioUnitSetProperty(audioInstance, - kAudioUnitProperty_SetRenderCallback, - kAudioUnitScope_Input, - 0, - &input, - sizeof(input)); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // setup the audio format we'll be using (stereo pcm) - AudioStreamBasicDescription streamFormat; - memset(&streamFormat, 0, sizeof(streamFormat)); - streamFormat.mSampleRate = SAMPLE_RATE; - streamFormat.mFormatID = kAudioFormatLinearPCM; - streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; - streamFormat.mBitsPerChannel = sizeof(AudioSampleType) * 8; - streamFormat.mChannelsPerFrame = 2; - streamFormat.mFramesPerPacket = 1; - streamFormat.mBytesPerFrame = (streamFormat.mBitsPerChannel / 8) * streamFormat.mChannelsPerFrame; - streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; - err = AudioUnitSetProperty(audioInstance, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Input, - 0, - &streamFormat, - sizeof(AudioStreamBasicDescription)); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // k, all setup, so init - err = AudioUnitInitialize(audioInstance); - if (err != noErr) { - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // finally start playback - err = AudioOutputUnitStart(audioInstance); - if (err != noErr) { - AudioUnitUninitialize(audioInstance); - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - return; - } - - // we're good to go - } -} - -void iOSCoreAudioShutdown() -{ - if (audioInstance) { - AudioOutputUnitStop(audioInstance); - AudioUnitUninitialize(audioInstance); - AudioComponentInstanceDispose(audioInstance); - audioInstance = nil; - } -} diff --git a/ios/iOSCoreAudio.mm b/ios/iOSCoreAudio.mm new file mode 100644 index 0000000000..b4f828f570 --- /dev/null +++ b/ios/iOSCoreAudio.mm @@ -0,0 +1,168 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +// This code implements the emulated audio using CoreAudio for iOS +// Originally written by jtraynham + +#include "iOSCoreAudio.h" + +#include "Common/Log.h" + +#include +#import + + +#define SAMPLE_RATE 44100 + +AudioComponentInstance audioInstance = nil; + +int NativeMix(short *audio, int num_samples); + +OSStatus iOSCoreAudioCallback(void *inRefCon, + AudioUnitRenderActionFlags *ioActionFlags, + const AudioTimeStamp *inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList *ioData) +{ + // see if we have any sound to play + short *output = (short *)ioData->mBuffers[0].mData; + UInt32 framesReady = NativeMix(output, inNumberFrames); + + if (framesReady == 0) { + // oops, we don't currently have any sound, so return silence + *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence; + } + + /* + * You'd think iOS would want to know how many frames were + * actually generated in case it was less than asked for, but + * apparently that causes micro-stuttering and everything just + * works better if we lie and say we successfully generated as + * many frames as it wanted... weird. We still get micro-stuttering + * but it's less noticeable this way. + */ + //UInt32 bytesReady = framesReady * sizeof(short) * 2; + UInt32 bytesReady = inNumberFrames * sizeof(short) * 2; + ioData->mBuffers[0].mDataByteSize = bytesReady; + + return noErr; +} + +void iOSCoreAudioInit() +{ + NSError *error = nil; + AVAudioSession *session = [AVAudioSession sharedInstance]; + if (![session setActive:YES error:&error]) { + ERROR_LOG(SYSTEM, "Failed to activate AVFoundation audio session"); + if (error.localizedDescription) { + NSLog(@"%@", error.localizedDescription); + } + if (error.localizedFailureReason) { + NSLog(@"%@", error.localizedFailureReason); + } + } + + if (!audioInstance) { + OSErr err; + + // first, grab the default output + AudioComponentDescription defaultOutputDescription; + defaultOutputDescription.componentType = kAudioUnitType_Output; + defaultOutputDescription.componentSubType = kAudioUnitSubType_RemoteIO; + defaultOutputDescription.componentManufacturer = kAudioUnitManufacturer_Apple; + defaultOutputDescription.componentFlags = 0; + defaultOutputDescription.componentFlagsMask = 0; + AudioComponent defaultOutput = AudioComponentFindNext(NULL, &defaultOutputDescription); + + // create our instance + err = AudioComponentInstanceNew(defaultOutput, &audioInstance); + if (err != noErr) { + audioInstance = nil; + return; + } + + // create our callback so we can give it the audio data + AURenderCallbackStruct input; + input.inputProc = iOSCoreAudioCallback; + input.inputProcRefCon = NULL; + err = AudioUnitSetProperty(audioInstance, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &input, + sizeof(input)); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // setup the audio format we'll be using (stereo pcm) + AudioStreamBasicDescription streamFormat; + memset(&streamFormat, 0, sizeof(streamFormat)); + streamFormat.mSampleRate = SAMPLE_RATE; + streamFormat.mFormatID = kAudioFormatLinearPCM; + streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + streamFormat.mBitsPerChannel = sizeof(short) * 8; + streamFormat.mChannelsPerFrame = 2; + streamFormat.mFramesPerPacket = 1; + streamFormat.mBytesPerFrame = (streamFormat.mBitsPerChannel / 8) * streamFormat.mChannelsPerFrame; + streamFormat.mBytesPerPacket = streamFormat.mBytesPerFrame * streamFormat.mFramesPerPacket; + err = AudioUnitSetProperty(audioInstance, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &streamFormat, + sizeof(AudioStreamBasicDescription)); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // k, all setup, so init + err = AudioUnitInitialize(audioInstance); + if (err != noErr) { + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // finally start playback + err = AudioOutputUnitStart(audioInstance); + if (err != noErr) { + AudioUnitUninitialize(audioInstance); + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + return; + } + + // we're good to go + } +} + +void iOSCoreAudioShutdown() +{ + if (audioInstance) { + AudioOutputUnitStop(audioInstance); + AudioUnitUninitialize(audioInstance); + AudioComponentInstanceDispose(audioInstance); + audioInstance = nil; + } +} + From 3af78883c73032fcda90cc52eadd75c226a0fa38 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 01:53:37 -0800 Subject: [PATCH 063/131] irjit: Speed up icache block invalidation. Turns out, in games using a ton of small memcpys, this was causing perf issues. --- Core/MIPS/IR/IRJit.cpp | 55 +++++++++++++++++++++++++++++++++--------- Core/MIPS/IR/IRJit.h | 16 +++++++++--- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index b36c40f110..f92f4f54ab 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -71,7 +71,8 @@ void IRJit::Compile(u32 em_address) { frontend_.DoJit(em_address, instructions, constants, mipsBytes); b->SetInstructions(instructions, constants); b->SetOriginalSize(mipsBytes); - b->Finalize(block_num); // Overwrites the first instruction + // Overwrites the first instruction, and also updates stats. + blocks_.FinalizeBlock(block_num); if (frontend_.CheckRounding()) { // Our assumptions are all wrong so it's clean-slate time. @@ -130,26 +131,56 @@ bool IRJit::ReplaceJalTo(u32 dest) { } void IRBlockCache::Clear() { - for (int i = 0; i < size_; ++i) { + for (int i = 0; i < (int)blocks_.size(); ++i) { blocks_[i].Destroy(i); } blocks_.clear(); + byPage_.clear(); } void IRBlockCache::InvalidateICache(u32 address, u32 length) { - // TODO: Could be more efficient. - for (int i = 0; i < size_; ++i) { - if (blocks_[i].OverlapsRange(address, length)) { - blocks_[i].Destroy(i); + u32 startPage = AddressToPage(address); + u32 endPage = AddressToPage(address + length); + + for (u32 page = startPage; page <= endPage; ++page) { + const auto iter = byPage_.find(page); + if (iter == byPage_.end()) + continue; + + const std::vector &blocksInPage = iter->second; + for (int i : blocksInPage) { + if (blocks_[i].OverlapsRange(address, length)) { + // Not removing from the page, hopefully doesn't build up with small recompiles. + blocks_[i].Destroy(i); + } } } } +void IRBlockCache::FinalizeBlock(int i) { + blocks_[i].Finalize(i); + + u32 startAddr, size; + blocks_[i].GetRange(startAddr, size); + + u32 startPage = AddressToPage(startAddr); + u32 endPage = AddressToPage(startAddr + size); + + for (u32 page = startPage; page <= endPage; ++page) { + byPage_[page].push_back(i); + } +} + +u32 IRBlockCache::AddressToPage(u32 addr) { + // Use relatively small pages since basic blocks are typically small. + return (addr & 0x3FFFFFFF) >> 10; +} + std::vector IRBlockCache::SaveAndClearEmuHackOps() { std::vector result; - result.resize(size_); + result.resize(blocks_.size()); - for (int number = 0; number < size_; ++number) { + for (int number = 0; number < (int)blocks_.size(); ++number) { IRBlock &b = blocks_[number]; if (b.IsValid() && b.RestoreOriginalFirstOp(number)) { result[number] = number; @@ -162,12 +193,12 @@ std::vector IRBlockCache::SaveAndClearEmuHackOps() { } void IRBlockCache::RestoreSavedEmuHackOps(std::vector saved) { - if (size_ != (int)saved.size()) { + if ((int)blocks_.size() != (int)saved.size()) { ERROR_LOG(JIT, "RestoreSavedEmuHackOps: Wrong saved block size."); return; } - for (int number = 0; number < size_; ++number) { + for (int number = 0; number < (int)blocks_.size(); ++number) { IRBlock &b = blocks_[number]; // Only if we restored it, write it back. if (b.IsValid() && saved[number] != 0 && b.HasOriginalFirstOp()) { @@ -207,7 +238,9 @@ void IRBlock::Destroy(int number) { } bool IRBlock::OverlapsRange(u32 addr, u32 size) { - return addr + size > origAddr_ && addr < origAddr_ + origSize_; + addr &= 0x3FFFFFFF; + u32 origAddr = origAddr_ & 0x3FFFFFFF; + return addr + size > origAddr && addr < origAddr + origSize_; } MIPSOpcode IRJit::GetOriginalOp(MIPSOpcode op) { diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 67fa3a993c..2e3f57a687 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include "Common/Common.h" #include "Common/CPUDetect.h" @@ -81,6 +82,11 @@ public: } bool OverlapsRange(u32 addr, u32 size); + void GetRange(u32 &start, u32 &size) { + start = origAddr_; + size = origSize_; + } + void Finalize(int number); void Destroy(int number); @@ -96,17 +102,17 @@ private: class IRBlockCache { public: - IRBlockCache() : size_(0) {} + IRBlockCache() {} void Clear(); void InvalidateICache(u32 address, u32 length); + void FinalizeBlock(int i); int GetNumBlocks() const { return (int)blocks_.size(); } int AllocateBlock(int emAddr) { blocks_.push_back(IRBlock(emAddr)); - size_ = (int)blocks_.size(); return (int)blocks_.size() - 1; } IRBlock *GetBlock(int i) { - if (i >= 0 && i < size_) { + if (i >= 0 && i < (int)blocks_.size()) { return &blocks_[i]; } else { return nullptr; @@ -117,8 +123,10 @@ public: void RestoreSavedEmuHackOps(std::vector saved); private: - int size_; // Hm, is this a cache for speed in debug mode, or what? + u32 AddressToPage(u32 addr); + std::vector blocks_; + std::unordered_map> byPage_; }; class IRJit : public JitInterface { From 5b857f08ed4762bee66cd204e37352ed995fdb80 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 18:00:14 -0800 Subject: [PATCH 064/131] Create AppDelegate the standard way instead of in PPSSPPUIApplication init --- ios/PPSSPPUIApplication.mm | 6 ------ ios/main.mm | 3 +-- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/ios/PPSSPPUIApplication.mm b/ios/PPSSPPUIApplication.mm index c535081b99..d07e7a26e0 100644 --- a/ios/PPSSPPUIApplication.mm +++ b/ios/PPSSPPUIApplication.mm @@ -60,12 +60,6 @@ @implementation PPSSPPUIApplication -- (instancetype)init { - auto instance = [super init]; - instance.delegate = [[AppDelegate alloc] init]; - return instance; -} - - (void)decodeKeyEvent:(NSInteger *)eventMem { NSInteger eventType = eventMem[GSEVENT_TYPE]; NSInteger eventScanCode = eventMem[GSEVENTKEY_KEYCODE]; diff --git a/ios/main.mm b/ios/main.mm index 3070e772d6..c6eb2d3c7f 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -113,7 +113,6 @@ int main(int argc, char *argv[]) // Simulates a debugger. Makes it possible to use JIT (though only W^X) syscall(SYS_ptrace, 0 /*PTRACE_TRACEME*/, 0, 0, 0); @autoreleasepool { - return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), nil); - //return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), NSStringFromClass([AppDelegate class])); } } From c563166f9aefdd9e908dde5b40342b660bd5ec7f Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 18:26:47 -0800 Subject: [PATCH 065/131] Modernize property syntax and don't use viewDidUnload (deprecated in iOS 6) --- ios/ViewController.mm | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 528287094c..4fd1c60739 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -74,10 +74,10 @@ static GraphicsContext *graphicsContext; std::map iCadeToKeyMap; } -@property (nonatomic) EAGLContext* context; -@property (nonatomic) NSString* documentsPath; -@property (nonatomic) NSString* bundlePath; -@property (nonatomic) NSMutableArray* touches; +@property (nonatomic, strong) EAGLContext* context; +@property (nonatomic, strong) NSString* documentsPath; +@property (nonatomic, strong) NSString* bundlePath; +@property (nonatomic, strong) NSMutableArray* touches; @property (nonatomic) AudioEngine* audioEngine; //@property (nonatomic) iCadeReaderView* iCadeView; #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 @@ -214,16 +214,6 @@ static GraphicsContext *graphicsContext; #endif } -- (void)viewDidUnload -{ - [super viewDidUnload]; - - if ([EAGLContext currentContext] == self.context) { - [EAGLContext setCurrentContext:nil]; - } - self.context = nil; -} - - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; @@ -231,7 +221,10 @@ static GraphicsContext *graphicsContext; - (void)dealloc { - [self viewDidUnload]; + if ([EAGLContext currentContext] == self.context) { + [EAGLContext setCurrentContext:nil]; + } + self.context = nil; #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 if ([GCController class]) { From 4a119beb59c49d91df912c5e2badedd80c2be1bf Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 21:57:29 -0800 Subject: [PATCH 066/131] iOS: fix a reference counting bug that may have caused ViewController to never be deallocated --- ios/ViewController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 4fd1c60739..2c9bbf436d 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -66,7 +66,7 @@ extern ScreenManager *screenManager; extern bool iosCanUseJit; extern bool targetIsJailbroken; -ViewController* sharedViewController; +__unsafe_unretained static ViewController* sharedViewController; static GraphicsContext *graphicsContext; @interface ViewController () From 152dc2192f377f63056e79545822d074c83567bb Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sun, 31 Dec 2017 19:36:47 -0800 Subject: [PATCH 067/131] iOS: convert all obj-c source code to ARC --- CMakeLists.txt | 5 +++++ ios/iCade/iCadeReaderView.h | 4 ++-- ios/iCade/iCadeReaderView.m | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e5946a516b..a73684bf8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -692,6 +692,11 @@ elseif(IOS) set_source_files_properties(ios/AppDelegate.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) set_source_files_properties(ios/ViewController.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/iOSCoreAudio.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/PPSSPPUIApplication.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/iCade/iCadeReaderView.m PROPERTIES COMPILE_FLAGS -fobjc-arc) + set_source_files_properties(ios/main.mm PROPERTIES COMPILE_FLAGS -fobjc-arc) + set(TargetBin PPSSPP) elseif(USING_QT_UI) diff --git a/ios/iCade/iCadeReaderView.h b/ios/iCade/iCadeReaderView.h index 2f39a3dd52..8a6bb586f9 100755 --- a/ios/iCade/iCadeReaderView.h +++ b/ios/iCade/iCadeReaderView.h @@ -50,7 +50,7 @@ @interface iCadeReaderView : UIView { UIView *inputView; iCadeState _iCadeState; - id _delegate; + id __weak _delegate; struct { bool stateChanged:1; @@ -60,7 +60,7 @@ } @property (nonatomic, assign) iCadeState iCadeState; -@property (nonatomic, assign) id delegate; +@property (nonatomic, weak) id delegate; @property (nonatomic, assign) BOOL active; @end diff --git a/ios/iCade/iCadeReaderView.m b/ios/iCade/iCadeReaderView.m index 48a0a59df3..cd0e94fa59 100755 --- a/ios/iCade/iCadeReaderView.m +++ b/ios/iCade/iCadeReaderView.m @@ -49,7 +49,6 @@ static const char *OFF_STATES = "eczqtrfnmpgv"; - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - [super dealloc]; } - (void)didEnterBackground { From 67c6ecdecefe7042751e9e208ced73625e24b089 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sun, 31 Dec 2017 19:53:59 -0800 Subject: [PATCH 068/131] nil out sharedViewController in case bindDefaultFBO is called again --- ios/ViewController.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 2c9bbf436d..c49a550710 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -221,6 +221,8 @@ static GraphicsContext *graphicsContext; - (void)dealloc { + sharedViewController = nil; + if ([EAGLContext currentContext] == self.context) { [EAGLContext setCurrentContext:nil]; } From 0039fa8c0a305053c379b058a985311de41dc099 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 00:12:34 -0800 Subject: [PATCH 069/131] Logging: Use AndroidLogger for all mobile devices. Noticed iOS wasn't logging, which seems non-ideal considering Android does. Might as well make them behave the same. --- UI/NativeApp.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 07dc956c33..7ad706a0bc 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -191,10 +191,7 @@ int Win32Mix(short *buffer, int numSamples, int bits, int rate, int channels) { #endif // globals -#ifndef _WIN32 -static AndroidLogger *logger = 0; -#endif - +static AndroidLogger *logger = nullptr; std::string boot_filename = ""; void NativeHost::InitSound() { @@ -372,8 +369,6 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch LogManager::Init(); #ifndef _WIN32 - logger = new AndroidLogger(); - g_Config.AddSearchPath(user_data_path); g_Config.AddSearchPath(g_Config.memStickDirectory + "PSP/SYSTEM/"); g_Config.SetDefaultPath(g_Config.memStickDirectory + "PSP/SYSTEM/"); @@ -499,9 +494,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch logman->SetEnabled(type, true); logman->SetLogLevel(type, logLevel); } -#ifdef __ANDROID__ - logman->AddListener(logger); #endif + +#if defined(__ANDROID__) || (defined(MOBILE_DEVICE) && !defined(_DEBUG)) + // Enable basic logging for any kind of mobile device, since LogManager doesn't. + // The MOBILE_DEVICE/_DEBUG condition matches LogManager.cpp. + logger = new AndroidLogger(); + logman->AddListener(logger); #endif // Allow the lang directory to be overridden for testing purposes (e.g. Android, where it's hard to @@ -1139,6 +1138,9 @@ void NativeShutdown() { net::Shutdown(); + delete logger; + logger = nullptr; + // Previously we did exit() here on Android but that makes it hard to do things like restart on backend change. // I think we handle most globals correctly or correct-enough now. } From 8ffb0101fe8c401267ce43edef4eaa75b412fe53 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 16:37:49 -0800 Subject: [PATCH 070/131] jit: Report blocks with uneaten VFPU prefixes. There may be options to avoid, like continuing these blocks, especially if they're likely or something. --- Core/MIPS/ARM/ArmJit.cpp | 2 +- Core/MIPS/ARM64/Arm64Jit.cpp | 2 +- Core/MIPS/IR/IRFrontend.cpp | 4 ++-- Core/MIPS/IR/IRFrontend.h | 2 +- Core/MIPS/IR/IRJit.cpp | 2 +- Core/MIPS/x86/Jit.cpp | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index 0a7c3ad538..050cef8025 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -218,7 +218,7 @@ void ArmJit::Compile(u32 em_address) { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); js.LogPrefix(); // Let's try that one more time. We won't get back here because we toggled the value. diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index 86b80a3a57..e1aaea334f 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -204,7 +204,7 @@ void Arm64Jit::Compile(u32 em_address) { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); js.LogPrefix(); // Let's try that one more time. We won't get back here because we toggled the value. diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 17985ab835..7b8ab0f50d 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -105,7 +105,7 @@ void IRFrontend::CompileDelaySlot() { js.inDelaySlot = false; } -bool IRFrontend::CheckRounding() { +bool IRFrontend::CheckRounding(u32 blockAddress) { bool cleanSlate = false; if (js.hasSetRounding && !js.lastSetRounding) { WARN_LOG(JIT, "Detected rounding mode usage, rebuilding jit with checks"); @@ -116,7 +116,7 @@ bool IRFrontend::CheckRounding() { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block"); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block for %08x", blockAddress); logBlocks = 1; js.LogPrefix(); diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index b3fb8cdecf..e59cf3c70f 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -86,7 +86,7 @@ public: int Replace_fabsf() override; void DoState(PointerWrap &p); - bool CheckRounding(); // returns true if we need a do-over + bool CheckRounding(u32 blockAddress); // returns true if we need a do-over void DoJit(u32 em_address, std::vector &instructions, std::vector &constants, u32 &mipsBytes); diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index f92f4f54ab..7110ab7ea2 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -74,7 +74,7 @@ void IRJit::Compile(u32 em_address) { // Overwrites the first instruction, and also updates stats. blocks_.FinalizeBlock(block_num); - if (frontend_.CheckRounding()) { + if (frontend_.CheckRounding(em_address)) { // Our assumptions are all wrong so it's clean-slate time. ClearCache(); Compile(em_address); diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index e220336b2a..48e098cc3c 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -286,7 +286,7 @@ void Jit::Compile(u32 em_address) { // Drat. The VFPU hit an uneaten prefix at the end of a block. if (js.startDefaultPrefix && js.MayHavePrefix()) { - WARN_LOG(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); + WARN_LOG_REPORT(JIT, "An uneaten prefix at end of block: %08x", GetCompilerPC() - 4); js.LogPrefix(); // Let's try that one more time. We won't get back here because we toggled the value. From d8d174fa2b34bb23bbe42eceee514d0decbcc89d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 16:39:11 -0800 Subject: [PATCH 071/131] arm64jit: Avoid spilling an extra reg for lwl/lwr. It's only needed for swl and swr. --- Core/MIPS/ARM64/Arm64CompLoadStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index a3f44e9e89..9a226a32e6 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -180,7 +180,7 @@ namespace MIPSComp { gpr.SpillLock(rs); // Need to get temps before skipping safe mem. ARM64Reg LR_SCRATCH3 = gpr.GetAndLockTempR(); - ARM64Reg LR_SCRATCH4 = gpr.GetAndLockTempR(); + ARM64Reg LR_SCRATCH4 = o == 42 || o == 46 ? gpr.GetAndLockTempR() : INVALID_REG; if (!g_Config.bFastMemory && rs != MIPS_REG_SP) { skips = SetScratch1ForSafeAddress(rs, offset, SCRATCH2); From 905d2c2da6564a6976eac7c1c37a9ce6f0546ec1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 16:41:57 -0800 Subject: [PATCH 072/131] irjit: Cleanup some invalid op handling. And log blocks the same way as other backends. --- Core/MIPS/IR/IRCompALU.cpp | 22 ++++++++++++---------- Core/MIPS/IR/IRCompFPU.cpp | 21 ++++++--------------- Core/MIPS/IR/IRCompLoadStore.cpp | 5 ++++- Core/MIPS/IR/IRCompVFPU.cpp | 2 +- Core/MIPS/IR/IRFrontend.cpp | 16 ++++++++-------- 5 files changed, 31 insertions(+), 35 deletions(-) diff --git a/Core/MIPS/IR/IRCompALU.cpp b/Core/MIPS/IR/IRCompALU.cpp index c7074bed79..31dba9bfb9 100644 --- a/Core/MIPS/IR/IRCompALU.cpp +++ b/Core/MIPS/IR/IRCompALU.cpp @@ -40,14 +40,15 @@ // #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE ; #define DISABLE { Comp_Generic(op); return; } +#define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { void IRFrontend::Comp_IType(MIPSOpcode op) { CONDITIONAL_DISABLE; - s32 simm = (s32)(s16)(op & 0xFFFF); // sign extension - u32 uimm = op & 0xFFFF; + s32 simm = (s32)_IMM16; // sign extension + u32 uimm = (u16)_IMM16; u32 suimm = (u32)(s32)simm; MIPSGPReg rt = _RT; @@ -80,7 +81,7 @@ void IRFrontend::Comp_IType(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -104,7 +105,7 @@ void IRFrontend::Comp_RType2(MIPSOpcode op) { ir.Write(IROp::Clz, rd, IRTEMP_0); break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -176,7 +177,7 @@ void IRFrontend::Comp_RType3(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -213,8 +214,9 @@ void IRFrontend::Comp_ShiftType(MIPSOpcode op) { case 4: CompShiftVar(op, IROp::Shl, IROp::ShlImm); break; //sllv case 6: CompShiftVar(op, (sa == 1 ? IROp::Ror : IROp::Shr), (sa == 1 ? IROp::RorImm : IROp::ShrImm)); break; //srlv case 7: CompShiftVar(op, IROp::Sar, IROp::SarImm); break; //srav + default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -256,7 +258,7 @@ void IRFrontend::Comp_Special3(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -285,7 +287,7 @@ void IRFrontend::Comp_Allegrex(MIPSOpcode op) { break; default: - Comp_Generic(op); + INVALIDOP; return; } } @@ -307,7 +309,7 @@ void IRFrontend::Comp_Allegrex2(MIPSOpcode op) { ir.Write(IROp::BSwap32, rd, rt); break; default: - Comp_Generic(op); + INVALIDOP; break; } } @@ -372,7 +374,7 @@ void IRFrontend::Comp_MulDivType(MIPSOpcode op) { break; default: - DISABLE; + INVALIDOP; } } diff --git a/Core/MIPS/IR/IRCompFPU.cpp b/Core/MIPS/IR/IRCompFPU.cpp index 96c39acc8d..55fb033804 100644 --- a/Core/MIPS/IR/IRCompFPU.cpp +++ b/Core/MIPS/IR/IRCompFPU.cpp @@ -51,6 +51,7 @@ // #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE ; #define DISABLE { Comp_Generic(op); return; } +#define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { @@ -67,7 +68,7 @@ void IRFrontend::Comp_FPU3op(MIPSOpcode op) { case 2: ir.Write(IROp::FMul, fd, fs, ft); break; //F(fd) = F(fs) * F(ft); //mul case 3: ir.Write(IROp::FDiv, fd, fs, ft); break; //F(fd) = F(fs) / F(ft); //div default: - DISABLE; + INVALIDOP; return; } } @@ -90,7 +91,7 @@ void IRFrontend::Comp_FPULS(MIPSOpcode op) { break; default: - _dbg_assert_msg_(CPU, 0, "Trying to interpret FPULS instruction that can't be interpreted"); + INVALIDOP; break; } } @@ -131,7 +132,7 @@ void IRFrontend::Comp_FPUComp(MIPSOpcode op) { mode = IRFpCompareMode::LessEqualUnordered; break; default: - DISABLE; + INVALIDOP; return; } ir.Write(IROp::FCmp, (int)mode, fs, ft); @@ -158,27 +159,17 @@ void IRFrontend::Comp_FPU2op(MIPSOpcode op) { break; case 12: //FsI(fd) = (int)floorf(F(fs)+0.5f); break; //round.w.s - { ir.Write(IROp::FRound, fd, fs); break; - } - case 13: //FsI(fd) = Rto0(F(fs))); break; //trunc.w.s - { ir.Write(IROp::FTrunc, fd, fs); break; - } - case 14://FsI(fd) = (int)ceilf (F(fs)); break; //ceil.w.s - { ir.Write(IROp::FCeil, fd, fs); break; - } case 15: //FsI(fd) = (int)floorf(F(fs)); break; //floor.w.s - { ir.Write(IROp::FFloor, fd, fs); break; - } case 32: //F(fd) = (float)FsI(fs); break; //cvt.s.w ir.Write(IROp::FCvtSW, fd, fs); @@ -189,7 +180,7 @@ void IRFrontend::Comp_FPU2op(MIPSOpcode op) { break; default: - DISABLE; + INVALIDOP; } } @@ -234,7 +225,7 @@ void IRFrontend::Comp_mxc1(MIPSOpcode op) { } return; default: - DISABLE; + INVALIDOP; break; } } diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index 50130fc583..d956e13d07 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -96,13 +96,16 @@ namespace MIPSComp { case 46: //swr DISABLE; break; + default: - Comp_Generic(op); + INVALIDOP; return; } } void IRFrontend::Comp_Cache(MIPSOpcode op) { + CONDITIONAL_DISABLE; + // int imm = (s16)(op & 0xFFFF); // int rs = _RS; // int addr = R(rs) + imm; diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 5cc586a198..8638abae0b 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -1929,7 +1929,7 @@ namespace MIPSComp { ir.Write(IROp::FSub, tempregs[3], sregs[2], sregs[3]); } } else { - DISABLE; + INVALIDOP; } for (int i = 0; i < n; ++i) { diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 7b8ab0f50d..bdafffae96 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -279,32 +279,32 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v if (logBlocks > 0 && dontLogBlocks == 0) { char temp2[256]; - ILOG("=============== mips %08x ===============", em_address); + NOTICE_LOG(JIT, "=============== mips %08x ===============", em_address); for (u32 cpc = em_address; cpc != GetCompilerPC() + 4; cpc += 4) { temp2[0] = 0; MIPSDisAsm(Memory::Read_Opcode_JIT(cpc), cpc, temp2, true); - ILOG("M: %08x %s", cpc, temp2); + NOTICE_LOG(JIT, "M: %08x %s", cpc, temp2); } } if (logBlocks > 0 && dontLogBlocks == 0) { - ILOG("=============== Original IR (%d instructions, %d const) ===============", (int)ir.GetInstructions().size(), (int)ir.GetConstants().size()); + NOTICE_LOG(JIT, "=============== Original IR (%d instructions, %d const) ===============", (int)ir.GetInstructions().size(), (int)ir.GetConstants().size()); for (size_t i = 0; i < ir.GetInstructions().size(); i++) { char buf[256]; DisassembleIR(buf, sizeof(buf), ir.GetInstructions()[i], &ir.GetConstants()[0]); - ILOG("%s", buf); + NOTICE_LOG(JIT, "%s", buf); } - ILOG("=============== end ================="); + NOTICE_LOG(JIT, "=============== end ================="); } if (logBlocks > 0 && dontLogBlocks == 0) { - ILOG("=============== IR (%d instructions, %d const) ===============", (int)code->GetInstructions().size(), (int)code->GetConstants().size()); + NOTICE_LOG(JIT, "=============== IR (%d instructions, %d const) ===============", (int)code->GetInstructions().size(), (int)code->GetConstants().size()); for (size_t i = 0; i < code->GetInstructions().size(); i++) { char buf[256]; DisassembleIR(buf, sizeof(buf), code->GetInstructions()[i], &code->GetConstants()[0]); - ILOG("%s", buf); + NOTICE_LOG(JIT, "%s", buf); } - ILOG("=============== end ================="); + NOTICE_LOG(JIT, "=============== end ================="); } if (logBlocks > 0) From 671be241051d318a0ea18a892aaf9d9609106568 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 16:44:47 -0800 Subject: [PATCH 073/131] irjit: Add extra temps to make lwl/swl/etc. easier. --- Core/MIPS/IR/IRInst.cpp | 2 ++ Core/MIPS/IR/IRInst.h | 2 ++ Core/MIPS/IR/IRPassSimplify.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 2c216bf9c5..655a736cee 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -207,6 +207,8 @@ const char *GetGPRName(int 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"; default: return "(unk)"; diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 50d9a6707d..1506542177 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -275,6 +275,8 @@ enum IRFpCompareMode { enum { IRTEMP_0 = 192, IRTEMP_1, + IRTEMP_2, + IRTEMP_3, IRTEMP_LHS, // Reserved for use in branches IRTEMP_RHS, // Reserved for use in branches diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index aba843689b..c8fdbafbbc 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -662,6 +662,8 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out) { switch (dest) { case IRTEMP_0: case IRTEMP_1: + case IRTEMP_2: + case IRTEMP_3: case IRTEMP_LHS: case IRTEMP_RHS: // Unlike other ops, these don't need to persist between blocks. From b37ba9e5994657bf272ba6c50c758af1897e611f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 17:14:34 -0800 Subject: [PATCH 074/131] irjit: Add options for compile/optimize steps. This way the backend can set flags for the type of IR it wants. It's seems too complex to combine certain things like lwl/lwr in a pass. --- Core/MIPS/IR/IRFrontend.cpp | 4 +--- Core/MIPS/IR/IRFrontend.h | 9 +++++++-- Core/MIPS/IR/IRInst.h | 4 ++++ Core/MIPS/IR/IRJit.cpp | 4 ++++ Core/MIPS/IR/IRPassSimplify.cpp | 32 +++++++++++++++----------------- Core/MIPS/IR/IRPassSimplify.h | 18 +++++++++--------- 6 files changed, 40 insertions(+), 31 deletions(-) diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index bdafffae96..d3da613ca4 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -32,8 +32,6 @@ namespace MIPSComp { IRFrontend::IRFrontend(bool startDefaultPrefix) { - logBlocks = 0; - dontLogBlocks = 0; js.startDefaultPrefix = true; js.hasSetRounding = false; // js.currentRoundingFunc = convertS0ToSCRATCH1[0]; @@ -267,7 +265,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v // &MergeLoadStore, // &ThreeOpToTwoOp, }; - if (IRApplyPasses(passes, ARRAY_SIZE(passes), ir, simplified)) + if (IRApplyPasses(passes, ARRAY_SIZE(passes), ir, simplified, opts)) logBlocks = 1; code = &simplified; //if (ir.GetInstructions().size() >= 24) diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index e59cf3c70f..600177be20 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -94,6 +94,10 @@ public: js.EatPrefix(); } + void SetOptions(const IROptions &o) { + opts = o; + } + private: void RestoreRoundingMode(bool force = false); void ApplyRoundingMode(bool force = false); @@ -134,9 +138,10 @@ private: // State JitState js; IRWriter ir; + IROptions opts{}; - int dontLogBlocks; - int logBlocks; + int dontLogBlocks = 0; + int logBlocks = 0; }; } // namespace diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 1506542177..a54e4cab93 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -363,6 +363,10 @@ private: std::vector constPool_; }; +struct IROptions { + bool unalignedLoadStore; +}; + const IRMeta *GetIRMeta(IROp op); void DisassembleIR(char *buf, size_t bufsize, IRInst inst, const u32 *constPool); void InitIR(); diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 7110ab7ea2..84db03b150 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -41,6 +41,10 @@ IRJit::IRJit(MIPSState *mips) : frontend_(mips->HasDefaultPrefix()), mips_(mips) u32 size = 128 * 1024; // blTrampolines_ = kernelMemory.Alloc(size, true, "trampoline"); InitIR(); + + IROptions opts{}; + opts.unalignedLoadStore = true; + frontend_.SetOptions(opts); } IRJit::~IRJit() { diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index c8fdbafbbc..c8e728f45c 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -110,9 +110,9 @@ IROp ShiftToShiftImm(IROp op) { } } -bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out) { +bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out, const IROptions &opts) { if (c == 1) { - return passes[0](in, out); + return passes[0](in, out, opts); } bool logBlocks = false; @@ -121,7 +121,7 @@ bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWri const IRWriter *nextIn = ∈ IRWriter *nextOut = &temp[1]; for (size_t i = 0; i < c - 1; ++i) { - if (passes[i](*nextIn, *nextOut)) { + if (passes[i](*nextIn, *nextOut, opts)) { logBlocks = true; } @@ -129,14 +129,14 @@ bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWri nextIn = &temp[0]; } - if (passes[c - 1](*nextIn, out)) { + if (passes[c - 1](*nextIn, out, opts)) { logBlocks = true; } return logBlocks; } -bool OptimizeFPMoves(const IRWriter &in, IRWriter &out) { +bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts) { const u32 *constants = !in.GetConstants().empty() ? &in.GetConstants()[0] : nullptr; bool logBlocks = false; IRInst prev; @@ -191,7 +191,7 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out) { } // Might be useful later on x86. -bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out) { +bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts) { bool logBlocks = false; for (int i = 0; i < (int)in.GetInstructions().size(); i++) { IRInst inst = in.GetInstructions()[i]; @@ -245,7 +245,7 @@ bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out) { return logBlocks; } -bool PropagateConstants(const IRWriter &in, IRWriter &out) { +bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts) { IRRegCache gpr(&out); const u32 *constants = !in.GetConstants().empty() ? &in.GetConstants()[0] : nullptr; @@ -619,7 +619,7 @@ int IRDestGPR(const IRInst &inst) { return -1; } -bool PurgeTemps(const IRWriter &in, IRWriter &out) { +bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { std::vector insts; insts.reserve(in.GetInstructions().size()); @@ -710,7 +710,7 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out) { return logBlocks; } -bool ReduceLoads(const IRWriter &in, IRWriter &out) { +bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { for (u32 value : in.GetConstants()) { out.AddConstant(value); } @@ -846,7 +846,7 @@ static std::vector ReorderLoadStoreOps(std::vector &ops, const u return ops; } -bool ReorderLoadStore(const IRWriter &in, IRWriter &out) { +bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { bool logBlocks = false; enum class RegState : u8 { @@ -1042,7 +1042,7 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out) { return logBlocks; } -bool MergeLoadStore(const IRWriter &in, IRWriter &out) { +bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { bool logBlocks = false; auto opsCompatible = [&](const IRInst &a, const IRInst &b, int dist) { @@ -1076,16 +1076,15 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out) { break; } } - // Warning: this may generate unaligned stores. - if (c == 2 || c == 3) { + if ((c == 2 || c == 3) && opts.unalignedLoadStore) { inst.op = IROp::Store16; out.Write(inst); prev = inst; - // Skip the next one. + // Skip the next one (the 3rd will be separate.) ++i; continue; } - if (c == 4) { + if (c == 4 && opts.unalignedLoadStore) { inst.op = IROp::Store32; out.Write(inst); prev = inst; @@ -1108,8 +1107,7 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out) { break; } } - // Warning: this may generate unaligned stores. - if (c == 2) { + if (c == 2 && opts.unalignedLoadStore) { inst.op = IROp::Store32; out.Write(inst); prev = inst; diff --git a/Core/MIPS/IR/IRPassSimplify.h b/Core/MIPS/IR/IRPassSimplify.h index aeb2cff238..118b44128f 100644 --- a/Core/MIPS/IR/IRPassSimplify.h +++ b/Core/MIPS/IR/IRPassSimplify.h @@ -2,14 +2,14 @@ #include "Core/MIPS/IR/IRInst.h" -typedef bool (*IRPassFunc)(const IRWriter &in, IRWriter &out); -bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out); +typedef bool (*IRPassFunc)(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out, const IROptions &opts); // Block optimizer passes of varying usefulness. -bool PropagateConstants(const IRWriter &in, IRWriter &out); -bool PurgeTemps(const IRWriter &in, IRWriter &out); -bool ReduceLoads(const IRWriter &in, IRWriter &out); -bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out); -bool OptimizeFPMoves(const IRWriter &in, IRWriter &out); -bool ReorderLoadStore(const IRWriter &in, IRWriter &out); -bool MergeLoadStore(const IRWriter &in, IRWriter &out); +bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts); +bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts); From 3abcc4d6d8442ca3a2b2e7a1751586c4a67b1eee Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 31 Dec 2017 17:21:41 -0800 Subject: [PATCH 075/131] irjit: Implement lwl/lwr/swl/swr. This is very similar to the arm64jit implementation. --- Core/MIPS/IR/IRCompLoadStore.cpp | 117 +++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index d956e13d07..31beef109d 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -41,16 +41,124 @@ // #define CONDITIONAL_DISABLE { Comp_Generic(op); return; } #define CONDITIONAL_DISABLE ; #define DISABLE { Comp_Generic(op); return; } +#define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { void IRFrontend::Comp_ITypeMemLR(MIPSOpcode op, bool load) { - DISABLE; + CONDITIONAL_DISABLE; + + int offset = _IMM16; + MIPSGPReg rt = _RT; + MIPSGPReg rs = _RS; + int o = op >> 26; + + if (!js.inDelaySlot && opts.unalignedLoadStore) { + // Optimisation: Combine to single unaligned load/store. + const bool isLeft = (o == 34 || o == 42); + MIPSOpcode nextOp = GetOffsetInstruction(1); + // Find a matching shifted load/store in opposite direction with opposite offset. + if (nextOp == (isLeft ? (op.encoding + (4 << 26) - 3) : (op.encoding - (4 << 26) + 3))) { + EatInstruction(nextOp); + + if (isLeft) { + // Get the unaligned base offset from the lwr/swr instruction. + offset = (signed short)(nextOp & 0xFFFF); + // Already checked it if we're on the lwr. + CheckMemoryBreakpoint(rs, offset); + } + + if (load) { + ir.Write(IROp::Load32, rt, rs, ir.AddConstant(offset)); + } else { + ir.Write(IROp::Store32, rt, rs, ir.AddConstant(offset)); + } + return; + } + } + + int addrReg = IRTEMP_0; + int valueReg = IRTEMP_1; + int maskReg = IRTEMP_2; + int shiftReg = IRTEMP_3; + + // addrReg = rs + imm + ir.Write(IROp::AddConst, addrReg, rs, ir.AddConstant(offset)); + // shiftReg = (addr & 3) * 8 + ir.Write(IROp::AndConst, shiftReg, addrReg, ir.AddConstant(3)); + ir.Write(IROp::ShlImm, shiftReg, shiftReg, 3); + // addrReg = addr & 0xfffffffc (for stores, later) + ir.Write(IROp::AndConst, addrReg, addrReg, ir.AddConstant(0xFFFFFFFC)); + // valueReg = RAM(addrReg) + ir.Write(IROp::Load32, valueReg, addrReg, ir.AddConstant(0)); + + switch (o) { + case 34: //lwl + // rt &= (0x00ffffff >> shift) + // Alternatively, could shift to a wall and back (but would require two shifts each way.) + ir.WriteSetConstant(maskReg, 0x00ffffff); + ir.Write(IROp::Shr, maskReg, maskReg, shiftReg); + ir.Write(IROp::And, rt, rt, maskReg); + // valueReg <<= (24 - shift) + ir.Write(IROp::Neg, shiftReg, shiftReg); + ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); + ir.Write(IROp::Shl, valueReg, valueReg, shiftReg); + // rt |= valueReg + ir.Write(IROp::Or, rt, rt, valueReg); + break; + case 38: //lwr + // valueReg >>= shift + ir.Write(IROp::Shr, valueReg, valueReg, shiftReg); + // shiftReg = 24 - shift + ir.Write(IROp::Neg, shiftReg, shiftReg); + ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); + // rt &= (0xffffff00 << (24 - shift)) + // Alternatively, could shift to a wall and back (but would require two shifts each way.) + ir.WriteSetConstant(maskReg, 0xffffff00); + ir.Write(IROp::Shl, maskReg, maskReg, shiftReg); + ir.Write(IROp::And, rt, rt, maskReg); + // rt |= valueReg + ir.Write(IROp::Or, rt, rt, valueReg); + break; + case 42: //swl + // valueReg &= 0xffffff00 << shift + ir.WriteSetConstant(maskReg, 0xffffff00); + ir.Write(IROp::Shl, maskReg, maskReg, shiftReg); + ir.Write(IROp::And, valueReg, valueReg, maskReg); + // shiftReg = 24 - shift + ir.Write(IROp::Neg, shiftReg, shiftReg); + ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); + // valueReg |= rt >> (24 - shift) + ir.Write(IROp::Shr, maskReg, rt, shiftReg); + ir.Write(IROp::Or, valueReg, valueReg, maskReg); + break; + case 46: //swr + // valueReg &= 0x00ffffff << (24 - shift) + ir.WriteSetConstant(maskReg, 0x00ffffff); + ir.Write(IROp::Neg, shiftReg, shiftReg); + ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); + ir.Write(IROp::Shl, maskReg, maskReg, shiftReg); + ir.Write(IROp::And, valueReg, valueReg, maskReg); + ir.Write(IROp::Neg, shiftReg, shiftReg); + ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); + // valueReg |= rt << shift + ir.Write(IROp::Shl, maskReg, rt, shiftReg); + ir.Write(IROp::Or, valueReg, valueReg, maskReg); + break; + default: + INVALIDOP; + return; + } + + if (!load) { + // RAM(addrReg) = valueReg + ir.Write(IROp::Store32, valueReg, addrReg, ir.AddConstant(0)); + } } void IRFrontend::Comp_ITypeMem(MIPSOpcode op) { CONDITIONAL_DISABLE; - int offset = (signed short)(op & 0xFFFF); + int offset = _IMM16; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; int o = op >> 26; @@ -61,7 +169,6 @@ namespace MIPSComp { CheckMemoryBreakpoint(rs, offset); - int addrReg = IRTEMP_0; switch (o) { // Load case 35: @@ -92,9 +199,11 @@ namespace MIPSComp { case 34: //lwl case 38: //lwr + Comp_ITypeMemLR(op, true); + break; case 42: //swl case 46: //swr - DISABLE; + Comp_ITypeMemLR(op, false); break; default: From 6509f8b4332f1704d0e7f4def38e28c22f43088e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 1 Jan 2018 08:57:08 -0800 Subject: [PATCH 076/131] HLE: Reset latestSyscall on save state load. Loading a save state might call functions which call HLE log, such as AtracSetContext. This was outputting confusing log / reporting messages based on a random recent syscall. --- Core/HLE/HLE.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index 8b63cfc401..01fdfa6c61 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -99,6 +99,8 @@ void HLEDoState(PointerWrap &p) if (!s) return; + // Can't be inside a syscall, reset this so errors aren't misleading. + latestSyscall = nullptr; p.Do(delayedResultEvent); CoreTiming::RestoreRegisterEvent(delayedResultEvent, "HLEDelayedResult", hleDelayResultFinish); } @@ -656,11 +658,18 @@ size_t hleFormatLogArgs(char *message, size_t sz, const char *argmask) { void hleDoLogInternal(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, u64 res, const char *file, int line, const char *reportTag, char retmask, const char *reason, const char *formatted_reason) { char formatted_args[4096]; - hleFormatLogArgs(formatted_args, sizeof(formatted_args), latestSyscall->argmask); + const char *funcName = "?"; + u32 funcFlags = 0; + if (latestSyscall) { + hleFormatLogArgs(formatted_args, sizeof(formatted_args), latestSyscall->argmask); - // This acts as an override (for error returns which are usually hex.) - if (retmask == '\0') - retmask = latestSyscall->retmask; + // This acts as an override (for error returns which are usually hex.) + if (retmask == '\0') + retmask = latestSyscall->retmask; + + funcName = latestSyscall->name; + funcFlags = latestSyscall->flags; + } const char *fmt; if (retmask == 'x') { @@ -677,19 +686,19 @@ void hleDoLogInternal(LogTypes::LOG_TYPE t, LogTypes::LOG_LEVELS level, u64 res, fmt = "%s%08llx=%s(%s)%s"; } - const char *kernelFlag = (latestSyscall->flags & HLE_KERNEL_SYSCALL) != 0 ? "K " : ""; - GenericLog(level, t, file, line, fmt, kernelFlag, res, latestSyscall->name, formatted_args, formatted_reason); + const char *kernelFlag = (funcFlags & HLE_KERNEL_SYSCALL) != 0 ? "K " : ""; + GenericLog(level, t, file, line, fmt, kernelFlag, res, funcName, formatted_args, formatted_reason); if (reportTag != nullptr) { // A blank string means always log, not just once. if (reportTag[0] == '\0' || Reporting::ShouldLogOnce(reportTag)) { // Here we want the original key, so that different args, etc. group together. - std::string key = std::string(kernelFlag) + std::string("%08x=") + latestSyscall->name + "(%s)"; + std::string key = std::string(kernelFlag) + std::string("%08x=") + funcName + "(%s)"; if (reason != nullptr) key += std::string(": ") + reason; char formatted_message[8192]; - snprintf(formatted_message, sizeof(formatted_message), fmt, kernelFlag, res, latestSyscall->name, formatted_args, formatted_reason); + snprintf(formatted_message, sizeof(formatted_message), fmt, kernelFlag, res, funcName, formatted_args, formatted_reason); Reporting::ReportMessageFormatted(key.c_str(), formatted_message); } } From 3e5833843ae24499ae413c5a01e74cb6ed65f87d Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Sat, 30 Dec 2017 18:19:01 -0800 Subject: [PATCH 077/131] Remove references to AudioEngine, which isn't used anyway --- ios/ViewController.mm | 2 -- 1 file changed, 2 deletions(-) diff --git a/ios/ViewController.mm b/ios/ViewController.mm index c49a550710..33431324cf 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -6,7 +6,6 @@ // #import "ViewController.h" -#import "AudioEngine.h" #import #include @@ -78,7 +77,6 @@ static GraphicsContext *graphicsContext; @property (nonatomic, strong) NSString* documentsPath; @property (nonatomic, strong) NSString* bundlePath; @property (nonatomic, strong) NSMutableArray* touches; -@property (nonatomic) AudioEngine* audioEngine; //@property (nonatomic) iCadeReaderView* iCadeView; #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 @property (nonatomic) GCController *gameController __attribute__((weak_import)); From 20794081eab467e25168a198c9c117b12ad1bbe8 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Mon, 1 Jan 2018 18:51:18 -0800 Subject: [PATCH 078/131] iOS: Nix iosCanUseJit and targetIsJailbroken. Move NativeInit call to main so it can take cmd line args. --- Core/Config.cpp | 14 ++------------ UI/GameSettingsScreen.cpp | 5 ----- UI/NativeApp.cpp | 5 ----- ios/ViewController.mm | 23 ----------------------- ios/main.mm | 8 +++++++- 5 files changed, 9 insertions(+), 46 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 78d8eb7b8f..744695e058 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -55,11 +55,6 @@ static const char *logSectionName = "LogDebug"; static const char *logSectionName = "Log"; #endif - -#ifdef IOS -extern bool iosCanUseJit; -#endif - struct ConfigSetting { enum Type { TYPE_TERMINATOR, @@ -314,11 +309,8 @@ static int DefaultNumWorkers() { return cpu_info.num_cores; } -// TODO: Default to IRJit on iOS when it's done. static int DefaultCpuCore() { -#ifdef IOS - return (int)(iosCanUseJit ? CPUCore::JIT : CPUCore::INTERPRETER); -#elif defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) +#if defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) return (int)CPUCore::JIT; #else return (int)CPUCore::INTERPRETER; @@ -326,9 +318,7 @@ static int DefaultCpuCore() { } static bool DefaultCodeGen() { -#ifdef IOS - return iosCanUseJit ? true : false; -#elif defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) +#if defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) return true; #else return false; diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index c82f9c1113..bef66c4c0f 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -67,11 +67,6 @@ #include "gfx/gl_common.h" #endif -#ifdef IOS -extern bool iosCanUseJit; -extern bool targetIsJailbroken; -#endif - extern bool VulkanMayBeAvailable(); GameSettingsScreen::GameSettingsScreen(std::string gamePath, std::string gameID, bool editThenRestore) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 7ad706a0bc..1c51566224 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -130,11 +130,6 @@ std::string config_filename; bool g_graphicsInited; -#ifdef IOS -bool iosCanUseJit; -bool targetIsJailbroken; -#endif - // Really need to clean this mess of globals up... but instead I add more :P bool g_TakeScreenshot; static bool isOuya; diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 33431324cf..6ba33c9328 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -62,9 +62,6 @@ bool simulateAnalog = false; extern ScreenManager *screenManager; -extern bool iosCanUseJit; -extern bool targetIsJailbroken; - __unsafe_unretained static ViewController* sharedViewController; static GraphicsContext *graphicsContext; @@ -98,26 +95,6 @@ static GraphicsContext *graphicsContext; if (self) { sharedViewController = self; self.touches = [NSMutableArray array]; - self.documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; - self.bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"]; - - iosCanUseJit = true; - targetIsJailbroken = false; - NSArray *jailPath = [NSArray arrayWithObjects: - @"/Applications/Cydia.app", - @"/private/var/lib/apt", - @"/private/var/stash", - @"/usr/sbin/sshd", - @"/usr/bin/sshd", nil]; - - for (NSString *string in jailPath) { - if ([[NSFileManager defaultManager] fileExistsAtPath:string]) { - // checking device jailbreak status in order to determine which message to show in GameSettingsScreen - targetIsJailbroken = true; - } - } - - NativeInit(0, NULL, [self.documentsPath UTF8String], [self.bundlePath UTF8String], NULL); iCadeToKeyMap[iCadeJoystickUp] = NKCODE_DPAD_UP; iCadeToKeyMap[iCadeJoystickRight] = NKCODE_DPAD_RIGHT; diff --git a/ios/main.mm b/ios/main.mm index c6eb2d3c7f..596e089bd5 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -112,7 +112,13 @@ int main(int argc, char *argv[]) { // Simulates a debugger. Makes it possible to use JIT (though only W^X) syscall(SYS_ptrace, 0 /*PTRACE_TRACEME*/, 0, 0, 0); + @autoreleasepool { - return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), NSStringFromClass([AppDelegate class])); + NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; + NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"]; + + NativeInit(argc, (const char**)argv, documentsPath.UTF8String, bundlePath.UTF8String, NULL); + + return UIApplicationMain(argc, argv, NSStringFromClass([PPSSPPUIApplication class]), NSStringFromClass([AppDelegate class])); } } From 873dcaa126949906e6289798b3164a1c8dd61e6a Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Mon, 1 Jan 2018 19:01:10 -0800 Subject: [PATCH 079/131] iOS: call PROFILE_INIT so we don't crash with USE_PROFILER --- ios/main.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/main.mm b/ios/main.mm index 596e089bd5..27f9cbac71 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -11,6 +11,7 @@ #import "PPSSPPUIApplication.h" #include "base/NativeApp.h" +#include "profiler/profiler.h" @interface UIApplication (Private) -(void) suspend; @@ -113,6 +114,8 @@ int main(int argc, char *argv[]) // Simulates a debugger. Makes it possible to use JIT (though only W^X) syscall(SYS_ptrace, 0 /*PTRACE_TRACEME*/, 0, 0, 0); + PROFILE_INIT(); + @autoreleasepool { NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *bundlePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:@"/assets/"]; From e3af9f401f1c5af5478eef206084da3857655b2c Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Mon, 1 Jan 2018 19:06:08 -0800 Subject: [PATCH 080/131] iOS: ViewController cleanups - delete unused code and properties --- ios/ViewController.mm | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/ios/ViewController.mm b/ios/ViewController.mm index 6ba33c9328..2ec8d7e7a7 100644 --- a/ios/ViewController.mm +++ b/ios/ViewController.mm @@ -30,10 +30,6 @@ #define IS_IPAD() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) #define IS_IPHONE() ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) -#ifndef kCFCoreFoundationVersionNumber_IOS_9_0 -#define kCFCoreFoundationVersionNumber_IOS_9_0 1240.10 -#endif - class IOSDummyGraphicsContext : public DummyGraphicsContext { public: IOSDummyGraphicsContext() { @@ -53,14 +49,12 @@ private: Draw::DrawContext *draw_; }; -float dp_xscale = 1.0f; -float dp_yscale = 1.0f; +static float dp_xscale = 1.0f; +static float dp_yscale = 1.0f; -double lastSelectPress = 0.0f; -double lastStartPress = 0.0f; -bool simulateAnalog = false; - -extern ScreenManager *screenManager; +static double lastSelectPress = 0.0f; +static double lastStartPress = 0.0f; +static bool simulateAnalog = false; __unsafe_unretained static ViewController* sharedViewController; static GraphicsContext *graphicsContext; @@ -71,8 +65,6 @@ static GraphicsContext *graphicsContext; } @property (nonatomic, strong) EAGLContext* context; -@property (nonatomic, strong) NSString* documentsPath; -@property (nonatomic, strong) NSString* bundlePath; @property (nonatomic, strong) NSMutableArray* touches; //@property (nonatomic) iCadeReaderView* iCadeView; #if __IPHONE_OS_VERSION_MAX_ALLOWED > __IPHONE_6_1 @@ -82,13 +74,6 @@ static GraphicsContext *graphicsContext; @end @implementation ViewController --(bool) isArm64 { - size_t size; - cpu_type_t type; - size = sizeof(type); - sysctlbyname("hw.cputype", &type, &size, NULL, 0); - return type == CPU_TYPE_ARM64; -} -(id) init { self = [super init]; From b11858d9a0851b888b00acaed427c1dac6bc318d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 1 Jan 2018 22:51:03 -0800 Subject: [PATCH 081/131] irjit: Properly account for delay slots in size. Otherwise we think blocks are 4 bytes too short, which can affect invalidation. --- Core/MIPS/IR/IRCompBranch.cpp | 20 ++++++++++++++++++++ Core/MIPS/IR/IRFrontend.cpp | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index f9185b3bdd..3a7affbd99 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -104,6 +104,8 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) { FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -141,6 +143,9 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink, // Taken FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -206,6 +211,9 @@ void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -261,6 +269,9 @@ void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) { // Taken FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -314,6 +325,9 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) { FlushAll(); ir.Write(IROp::ExitToConst, ir.AddConstant(targetAddr)); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } @@ -338,6 +352,9 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { ir.WriteSetConstant(rd, GetCompilerPC() + 8); CompileDelaySlot(); // Syscall (the delay slot) does FlushAll. + + // Account for the delay slot itself in total bytes. + js.compilerPC += 4; return; // Syscall (delay slot) wrote exit code. } else if (delaySlotIsNice) { if (andLink) @@ -371,6 +388,9 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { js.downcountAmount = 0; ir.Write(IROp::ExitToReg, 0, destReg, 0); + + // Account for the delay slot. + js.compilerPC += 4; js.compiling = false; } diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index d3da613ca4..fd602abc8d 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -278,7 +278,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v if (logBlocks > 0 && dontLogBlocks == 0) { char temp2[256]; NOTICE_LOG(JIT, "=============== mips %08x ===============", em_address); - for (u32 cpc = em_address; cpc != GetCompilerPC() + 4; cpc += 4) { + for (u32 cpc = em_address; cpc != GetCompilerPC(); cpc += 4) { temp2[0] = 0; MIPSDisAsm(Memory::Read_Opcode_JIT(cpc), cpc, temp2, true); NOTICE_LOG(JIT, "M: %08x %s", cpc, temp2); From b41413b8a5c5421257a7b9613fd893490f5756a2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 1 Jan 2018 19:52:24 -0800 Subject: [PATCH 082/131] Core: Asynchronously load the main ELF. Sometimes it takes a little time. More importantly, this allows us to load caches or do other things at start that might be a tad slow. Not doing anything like that yet, though. --- Core/PSPLoaders.cpp | 41 ++++++++++++++++++++++++++++++++++++++--- Core/System.cpp | 7 ++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 9df9f4e5ef..1a910edb05 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include "file/file_util.h" #include "util/text/utf8.h" @@ -250,7 +251,20 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { g_Config.loadGameConfig(id); host->SendUIMessage("config_loaded", ""); INFO_LOG(LOADER,"Loading %s...", bootpath.c_str()); - return __KernelLoadExec(bootpath.c_str(), 0, error_string); + + std::thread th([bootpath] { + // TODO: We can't use the initial error_string pointer. + bool success = __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString); + if (success && coreState == CORE_POWERUP) { + coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + } else { + coreState = CORE_ERROR; + // TODO: This is a crummy way to communicate the error... + PSP_CoreParameter().fileToStart = ""; + } + }); + th.detach(); + return true; } static std::string NormalizePath(const std::string &path) { @@ -354,13 +368,34 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { } // End of temporary code - return __KernelLoadExec(finalName.c_str(), 0, error_string); + std::thread th([finalName] { + bool success = __KernelLoadExec(finalName.c_str(), 0, &PSP_CoreParameter().errorString); + if (success && coreState == CORE_POWERUP) { + coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + } else { + coreState = CORE_ERROR; + // TODO: This is a crummy way to communicate the error... + PSP_CoreParameter().fileToStart = ""; + } + }); + th.detach(); + return true; } bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { BlobFileSystem *umd = new BlobFileSystem(&pspFileSystem, fileLoader, "data.ppdmp"); pspFileSystem.Mount("disc0:", umd); - __KernelLoadGEDump("disc0:/data.ppdmp", error_string); + std::thread th([] { + bool success = __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString); + if (success && coreState == CORE_POWERUP) { + coreState = PSP_CoreParameter().startPaused ? CORE_STEPPING : CORE_RUNNING; + } else { + coreState = CORE_ERROR; + // TODO: This is a crummy way to communicate the error... + PSP_CoreParameter().fileToStart = ""; + } + }); + th.detach(); return true; } diff --git a/Core/System.cpp b/Core/System.cpp index 1545174a6c..0e223ad79d 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -181,6 +181,8 @@ bool CPU_NextStateNot(CPUThreadState from, CPUThreadState to) { } bool CPU_IsReady() { + if (coreState == CORE_POWERUP) + return false; return cpuThreadState == CPU_THREAD_RUNNING || cpuThreadState == CPU_THREAD_NOT_RUNNING; } @@ -270,7 +272,8 @@ void CPU_Init() { // TODO: Check Game INI here for settings, patches and cheats, and modify coreParameter accordingly - // Why did we check for CORE_POWERDOWN here? + // If they shut down early, we'll catch it when load completes. + // Note: this may return before init is complete, which is checked if CPU_IsReady(). if (!LoadFile(&loadedFile, &coreParameter.errorString)) { CPU_Shutdown(); coreParameter.fileToStart = ""; @@ -282,8 +285,6 @@ void CPU_Init() { if (coreParameter.updateRecent) { g_Config.AddRecent(filename); } - - coreState = coreParameter.startPaused ? CORE_STEPPING : CORE_RUNNING; } void CPU_Shutdown() { From c793da500a1cba6a29b42f30ee59a72bc9aa28f2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 1 Jan 2018 21:51:09 -0800 Subject: [PATCH 083/131] UI: Show PIC1 while loading. In case it takes a second, less ugly than a black screen. --- UI/EmuScreen.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++------ UI/EmuScreen.h | 2 +- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index e442743a9a..0b143e1452 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -240,8 +240,6 @@ void EmuScreen::bootComplete() { host->BootDone(); host->UpdateDisassembly(); - g_gameInfoCache->FlushBGs(); - NOTICE_LOG(BOOT, "Loading %s...", PSP_CoreParameter().fileToStart.c_str()); autoLoad(); @@ -811,6 +809,37 @@ void EmuScreen::processAxis(const AxisInput &axis, int direction) { } } +class GameInfoBGView : public UI::InertView { +public: + GameInfoBGView(const std::string &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) { + } + + void Draw(UIContext &dc) { + // Should only be called when visible. + std::shared_ptr ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GAMEINFO_WANTBG); + dc.Flush(); + + // PIC1 is the loading image, so let's only draw if it's available. + if (ginfo && ginfo->pic1.texture) { + dc.GetDrawContext()->BindTexture(0, ginfo->pic1.texture->GetTexture()); + + double loadTime = ginfo->pic1.timeLoaded; + uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3)); + dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color); + dc.Flush(); + dc.RebindTexture(); + } + } + + void SetColor(uint32_t c) { + color_ = c; + } + +protected: + std::string gamePath_; + uint32_t color_ = 0xFFC0C0C0; +}; + void EmuScreen::CreateViews() { using namespace UI; @@ -831,14 +860,31 @@ void EmuScreen::CreateViews() { root_->Add(saveStatePreview_); root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h))); - loadingView_ = new TextView(sc->T("Loading game..."), new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true)); - root_->Add(loadingView_); + GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT))); + TextView *loadingTextView = root_->Add(new TextView(sc->T("Loading game..."), new AnchorLayoutParams(bounds.centerX(), bounds.centerY(), NONE, NONE, true))); + loadingTextView->SetShadow(true); + loadingView_ = loadingTextView; + + loadingViewColor_ = loadingTextView->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut)); + loadingViewColor_->SetCallback([loadingBG, loadingTextView](View *v, uint32_t c) { + loadingBG->SetColor(c & 0xFFC0C0C0); + loadingTextView->SetTextColor(c); + }); + loadingViewColor_->Persist(); // We start invisible here, in case of recreated views. - loadingViewColor_ = loadingView_->AddTween(new TextColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut)); - loadingViewColor_->Persist(); - loadingViewVisible_ = loadingView_->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut)); + loadingViewVisible_ = loadingTextView->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut)); loadingViewVisible_->Persist(); + loadingViewVisible_->Finish.Add([loadingBG](EventParams &p) { + loadingBG->SetVisibility(p.v->GetVisibility()); + + // If we just became invisible, flush BGs since we don't need them anymore. + // Saves some VRAM for the game, but don't do it before we fade out... + if (p.v->GetVisibility() == V_INVISIBLE) { + g_gameInfoCache->FlushBGs(); + } + return EVENT_DONE; + }); } UI::EventReturn EmuScreen::OnDevTools(UI::EventParams ¶ms) { diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 6cf8384294..1745e8728c 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -99,6 +99,6 @@ private: int saveStateSlot_; UI::View *loadingView_ = nullptr; - UI::TextColorTween *loadingViewColor_ = nullptr; + UI::CallbackColorTween *loadingViewColor_ = nullptr; UI::VisibilityTween *loadingViewVisible_ = nullptr; }; From d4400b358a1c1db2b26146e1c7ff66c77e75426c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 1 Jan 2018 22:06:20 -0800 Subject: [PATCH 084/131] UI: Actually offset text shadow. It wasn't actually visible before, since it was strictly underneath text that was usually white. --- UI/ReportScreen.cpp | 7 ++++--- ext/native/ui/view.cpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/UI/ReportScreen.cpp b/UI/ReportScreen.cpp index ce2556e5f7..549a86f604 100644 --- a/UI/ReportScreen.cpp +++ b/UI/ReportScreen.cpp @@ -62,11 +62,11 @@ private: RatingChoice::RatingChoice(const char *captionKey, int *value, LayoutParams *layoutParams) : LinearLayout(ORIENT_VERTICAL, layoutParams), value_(value) { - SetSpacing(-8.0f); + SetSpacing(0.0f); I18NCategory *rp = GetI18NCategory("Reporting"); group_ = new LinearLayout(ORIENT_HORIZONTAL); - Add(new InfoItem(rp->T(captionKey), "")); + Add(new TextView(rp->T(captionKey)))->SetShadow(true); Add(group_); group_->SetSpacing(0.0f); @@ -223,7 +223,7 @@ void ReportScreen::CreateViews() { ViewGroup *rightColumn = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(300, FILL_PARENT, actionMenuMargins)); LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); - leftColumnItems->Add(new TextView(rp->T("FeedbackDesc", "How's the emulation? Let us and the community know!"), new LinearLayoutParams(Margins(12, 5, 0, 5)))); + leftColumnItems->Add(new TextView(rp->T("FeedbackDesc", "How's the emulation? Let us and the community know!"), new LinearLayoutParams(Margins(12, 5, 0, 5))))->SetShadow(true); if (!Reporting::IsEnabled()) { reportingNotice_ = leftColumnItems->Add(new TextView(rp->T("FeedbackDisabled", "Compatibility server reports must be enabled."), new LinearLayoutParams(Margins(12, 5, 0, 5)))); reportingNotice_->SetShadow(true); @@ -258,6 +258,7 @@ void ReportScreen::CreateViews() { leftColumnItems->Add(new CompatRatingChoice("Overall", (int *)&overall_))->SetEnabledPtr(&enableReporting_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); overallDescription_ = leftColumnItems->Add(new TextView("", new LinearLayoutParams(Margins(10, 0)))); + overallDescription_->SetShadow(true); leftColumnItems->Add(new RatingChoice("Graphics", &graphics_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); leftColumnItems->Add(new RatingChoice("Speed", &speed_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); leftColumnItems->Add(new RatingChoice("Gameplay", &gameplay_))->SetEnabledPtr(&ratingEnabled_)->OnChoice.Handle(this, &ReportScreen::HandleChoice); diff --git a/ext/native/ui/view.cpp b/ext/native/ui/view.cpp index 367e7e04a9..0585b62d27 100644 --- a/ext/native/ui/view.cpp +++ b/ext/native/ui/view.cpp @@ -837,7 +837,7 @@ void TextView::Draw(UIContext &dc) { dc.SetFontStyle(small_ ? dc.theme->uiFontSmall : dc.theme->uiFont); if (shadow_) { uint32_t shadowColor = 0x80000000; - dc.DrawTextRect(text_.c_str(), bounds_, shadowColor, textAlign_); + dc.DrawTextRect(text_.c_str(), bounds_.Offset(1.0f, 1.0f), shadowColor, textAlign_); } uint32_t textColor = hasTextColor_ ? textColor_ : dc.theme->infoStyle.fgColor; dc.DrawTextRect(text_.c_str(), bounds_, textColor, textAlign_); From 9952d4bac561adecfc20851830b438d894486d6a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 1 Jan 2018 23:07:48 -0800 Subject: [PATCH 085/131] UI: Fade in loading much slower to avoid flicker. On Android, it often takes 0.3s or longer to load a game. Avoid fading it all the way in for just a short moment, if it's gonna be quick. --- UI/EmuScreen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 0b143e1452..439aaf3444 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -231,8 +231,8 @@ void EmuScreen::bootGame(const std::string &filename) { host->NotifyUserMessage(gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 15.0f); } - loadingViewColor_->Divert(0xFFFFFFFF, 0.15f); - loadingViewVisible_->Divert(UI::V_VISIBLE, 0.15f); + loadingViewColor_->Divert(0xFFFFFFFF, 0.75f); + loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f); } void EmuScreen::bootComplete() { From 30b2d05bac8b16a39c3ddd058243f730f929fa5a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 2 Jan 2018 21:53:13 -0800 Subject: [PATCH 086/131] Module: Correct detection of executable sections. --- Core/ELF/ElfReader.cpp | 11 ++++++++++ Core/ELF/ElfReader.h | 6 ++++-- Core/HLE/sceKernelModule.cpp | 41 ++++++++++++++++++------------------ Core/MIPS/MIPSAnalyst.cpp | 6 ++++-- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/Core/ELF/ElfReader.cpp b/Core/ELF/ElfReader.cpp index 9494604bd6..b68e7775b8 100644 --- a/Core/ELF/ElfReader.cpp +++ b/Core/ELF/ElfReader.cpp @@ -633,6 +633,17 @@ u32 ElfReader::GetTotalSectionSizeByPrefix(const std::string &prefix) const { return total; } +std::vector ElfReader::GetCodeSections() const { + std::vector ids; + for (int i = 0; i < GetNumSections(); ++i) { + u32 flags = sections[i].sh_flags; + if ((flags & (SHF_ALLOC | SHF_EXECINSTR)) == (SHF_ALLOC | SHF_EXECINSTR)) { + ids.push_back(i); + } + } + return ids; +} + bool ElfReader::LoadSymbols() { bool hasSymbols = false; diff --git a/Core/ELF/ElfReader.h b/Core/ELF/ElfReader.h index 78680c0f42..44d33a8e8e 100644 --- a/Core/ELF/ElfReader.h +++ b/Core/ELF/ElfReader.h @@ -17,9 +17,9 @@ #pragma once +#include #include "Common/CommonTypes.h" - -#include "ElfTypes.h" +#include "Core/ELF/ElfTypes.h" enum { R_MIPS_NONE, @@ -130,6 +130,8 @@ public: u32 GetTotalDataSize() const; u32 GetTotalSectionSizeByPrefix(const std::string &prefix) const; + std::vector GetCodeSections() const; + int LoadInto(u32 vaddr, bool fromTop); bool LoadSymbols(); bool LoadRelocations(const Elf32_Rel *rels, int numRelocs); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index d26eabf4f4..3448e5f6a9 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1238,18 +1238,6 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd module->nm.text_addr = module->textStart; module->nm.text_size = reader.GetTotalTextSize(); - - if (!module->isFake) { -#if !defined(MOBILE_DEVICE) - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); -#else - if (g_Config.bFuncReplacements) { - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); - } -#endif - } } else { module->nm.text_addr = 0; module->nm.text_size = 0; @@ -1269,17 +1257,28 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd if (textSection == -1) { module->textStart = reader.GetVaddr(); module->textEnd = firstImportStubAddr - 4; + } - if (!module->isFake) { -#if !defined(MOBILE_DEVICE) - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); -#else - if (g_Config.bFuncReplacements) { - bool gotSymbols = reader.LoadSymbols(); - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); - } + if (!module->isFake) { + bool scan = true; +#if defined(MOBILE_DEVICE) + scan = g_Config.bFuncReplacements; #endif + + bool gotSymbols = scan && reader.LoadSymbols(); + std::vector codeSections = reader.GetCodeSections(); + for (SectionID id : codeSections) { + u32 start = reader.GetSectionAddr(id); + u32 end = start + reader.GetSectionSize(id); + + if (start < module->textStart) + module->textStart = start; + if (end > module->textEnd) + module->textEnd = end; + + // Note: scan end is inclusive. + if (scan) + MIPSAnalyst::ScanForFunctions(start, end - 4, !gotSymbols); } } diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 3070c864b3..99afd042d3 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -1124,8 +1124,10 @@ skip: } } - currentFunction.end = addr + 4; - functions.push_back(currentFunction); + if (addr <= endAddr) { + currentFunction.end = addr + 4; + functions.push_back(currentFunction); + } for (auto iter = functions.begin(); iter != functions.end(); iter++) { iter->size = iter->end - iter->start + 4; From 00a05e97acacc51fe4b60e1d0896a4041380c364 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 2 Jan 2018 22:20:09 -0800 Subject: [PATCH 087/131] Module: Scan modules with no sections at all. Also, fix some off-by-one issues with end addresses. --- Core/HLE/sceKernelModule.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 3448e5f6a9..b65394ddb0 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1234,7 +1234,7 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd if (textSection != -1) { module->textStart = reader.GetSectionAddr(textSection); u32 textSize = reader.GetSectionSize(textSection); - module->textEnd = module->textStart + textSize; + module->textEnd = module->textStart + textSize - 4; module->nm.text_addr = module->textStart; module->nm.text_size = reader.GetTotalTextSize(); @@ -1269,16 +1269,21 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd std::vector codeSections = reader.GetCodeSections(); for (SectionID id : codeSections) { u32 start = reader.GetSectionAddr(id); - u32 end = start + reader.GetSectionSize(id); + // Note: scan end is inclusive. + u32 end = start + reader.GetSectionSize(id) - 4; if (start < module->textStart) module->textStart = start; if (end > module->textEnd) module->textEnd = end; - // Note: scan end is inclusive. if (scan) - MIPSAnalyst::ScanForFunctions(start, end - 4, !gotSymbols); + MIPSAnalyst::ScanForFunctions(start, end, !gotSymbols); + } + + // Some games don't have any sections at all. + if (scan && codeSections.empty()) { + MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); } } From 29ed48c32aabe03cb086634b6dd0a26c6a57d7f1 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 2 Jan 2018 22:21:08 -0800 Subject: [PATCH 088/131] Module: Avoid scanning stubs if possible. In this case, we often scan some garbage, but let's reduce it at least. --- Core/HLE/sceKernelModule.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index b65394ddb0..26d54d8794 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1283,7 +1283,18 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd // Some games don't have any sections at all. if (scan && codeSections.empty()) { - MIPSAnalyst::ScanForFunctions(module->textStart, module->textEnd, !gotSymbols); + u32 scanStart = module->textStart; + u32 scanEnd = module->textEnd; + // Skip the exports and imports sections, they're not code. + if (scanEnd >= std::min(modinfo->libent, modinfo->libstub)) { + MIPSAnalyst::ScanForFunctions(scanStart, std::min(modinfo->libent, modinfo->libstub) - 4, !gotSymbols); + scanStart = std::min(modinfo->libentend, modinfo->libstubend); + } + if (scanEnd >= std::max(modinfo->libent, modinfo->libstub)) { + MIPSAnalyst::ScanForFunctions(scanStart, std::max(modinfo->libent, modinfo->libstub) - 4, !gotSymbols); + scanStart = std::max(modinfo->libentend, modinfo->libstubend); + } + MIPSAnalyst::ScanForFunctions(scanStart, scanEnd, !gotSymbols); } } From e472947488909efe1b062b68ab8aa6769255ecab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 3 Jan 2018 16:30:39 +0100 Subject: [PATCH 089/131] VertexDecoderCommon: Avoid reading the destination during vertex decode, might be some kind of memory that's not good to read from... --- GPU/Common/VertexDecoderCommon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 4cf4d37287..8d37e72a54 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -502,28 +502,28 @@ void VertexDecoder::Step_Color5551() const { u8 *c = decoded_ + decFmt.c0off; u16 cdata = *(u16_le *)(ptr_ + coloff); + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (cdata >> 15) != 0; c[0] = Convert5To8(cdata & 0x1f); c[1] = Convert5To8((cdata >> 5) & 0x1f); c[2] = Convert5To8((cdata >> 10) & 0x1f); c[3] = (cdata >> 15) ? 255 : 0; - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] != 0; } void VertexDecoder::Step_Color4444() const { u8 *c = decoded_ + decFmt.c0off; u16 cdata = *(u16_le *)(ptr_ + coloff); + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (cdata >> 12) == 0xF; for (int j = 0; j < 4; j++) c[j] = Convert4To8((cdata >> (j * 4)) & 0xF); - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; } void VertexDecoder::Step_Color8888() const { u8 *c = decoded_ + decFmt.c0off; const u8 *cdata = (const u8*)(ptr_ + coloff); + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && cdata[3] == 255; memcpy(c, cdata, sizeof(u8) * 4); - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; } void VertexDecoder::Step_Color565Morph() const From 3ac2350ad69beb933de3d5f4c795e3e5e7b09e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 3 Jan 2018 16:31:55 +0100 Subject: [PATCH 090/131] IR Interpreter: Add a comment, minor cleanup, minor SSE stuff. --- Core/MIPS/IR/IRInst.h | 3 +++ Core/MIPS/IR/IRInterpreter.cpp | 30 ++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index a54e4cab93..4e8a5bcf61 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -314,6 +314,9 @@ struct IRMeta { }; // 32 bits. +// TODO: Evaluate whether it would make sense to switch to 64-bit ops with immediates +// included instead of storing immediates separately. Would simplify things at some memory +// storage and bandwidth cost. struct IRInst { IROp op; union { diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index f3a56ba51c..a245db1947 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -22,7 +22,7 @@ #include "Core/MIPS/IR/IRInterpreter.h" #include "Core/System.h" -alignas(16) float vec4InitValues[8][4] = { +alignas(16) static const float vec4InitValues[8][4] = { { 0.0f, 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { -1.0f, -1.0f, -1.0f, -1.0f }, @@ -32,6 +32,14 @@ alignas(16) float vec4InitValues[8][4] = { { 0.0f, 0.0f, 0.0f, 1.0f }, }; +alignas(16) static const uint32_t signBits[4] = { + 0x80000000, 0x80000000, 0x80000000, 0x80000000, +}; + +alignas(16) static const uint32_t noSignMask[4] = { + 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, +}; + u32 RunBreakpoint(u32 pc) { // Should we skip this breakpoint? if (CBreakPoints::CheckSkipFirst() == pc) @@ -238,13 +246,21 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::Vec4Neg: +#if defined(_M_SSE) + _mm_store_ps(&mips->f[inst->dest], _mm_xor_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)signBits))); +#else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = -mips->f[inst->src1 + i]; +#endif break; case IROp::Vec4Abs: +#if defined(_M_SSE) + _mm_store_ps(&mips->f[inst->dest], _mm_and_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)noSignMask))); +#else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = fabsf(mips->f[inst->src1 + i]); +#endif break; case IROp::Vec2Unpack16To31: @@ -433,8 +449,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c u32 x = mips->r[inst->src1]; int sa = mips->r[inst->src2] & 31; mips->r[inst->dest] = (x >> sa) | (x << (32 - sa)); + break; } - break; case IROp::Clz: { @@ -690,13 +706,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::FCvtWS: { float src = mips->f[inst->src1]; - if (my_isnanorinf(src)) - { + if (my_isnanorinf(src)) { mips->fs[inst->dest] = my_isinf(src) && src < 0.0f ? -2147483648LL : 2147483647LL; break; } - switch (mips->fcr31 & 3) - { + switch (mips->fcr31 & 3) { case 0: mips->fs[inst->dest] = (int)round_ieee_754(src); break; // RINT_0 case 1: mips->fs[inst->dest] = (int)src; break; // CAST_1 case 2: mips->fs[inst->dest] = (int)ceilf(src); break; // CEIL_2 @@ -760,7 +774,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::Syscall: - // SetPC was executed before. + // IROp::SetPC was (hopefully) executed before. { MIPSOpcode op(constPool[inst->src1]); CallSyscall(op); @@ -772,7 +786,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::ExitToPC: return mips->pc; - case IROp::Interpret: // SLOW fallback. Can be made faster. + case IROp::Interpret: // SLOW fallback. Can be made faster. Ideally should be removed but may be useful for debugging. { MIPSOpcode op(constPool[inst->src1]); MIPSInterpret(op); From e7893001e981fb3acf436763377c109d634e871e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 3 Jan 2018 17:02:07 +0100 Subject: [PATCH 091/131] VertexDecoderCommon: A few more cases. --- GPU/Common/VertexDecoderCommon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 8d37e72a54..e245917341 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -559,7 +559,7 @@ void VertexDecoder::Step_Color5551Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] == 255; } void VertexDecoder::Step_Color4444Morph() const @@ -575,7 +575,7 @@ void VertexDecoder::Step_Color4444Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] == 255; } void VertexDecoder::Step_Color8888Morph() const @@ -591,7 +591,7 @@ void VertexDecoder::Step_Color8888Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && c[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] == 255; } void VertexDecoder::Step_NormalS8() const From 64b57a032972bd8035cd44ff66e7c41652423edc Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 3 Jan 2018 08:14:25 -0800 Subject: [PATCH 092/131] irjit: Fix swr typo. Shifting the wrong direction - oops. --- Core/MIPS/IR/IRCompLoadStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index 31beef109d..cc17467ffd 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -136,7 +136,7 @@ namespace MIPSComp { ir.WriteSetConstant(maskReg, 0x00ffffff); ir.Write(IROp::Neg, shiftReg, shiftReg); ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); - ir.Write(IROp::Shl, maskReg, maskReg, shiftReg); + ir.Write(IROp::Shr, maskReg, maskReg, shiftReg); ir.Write(IROp::And, valueReg, valueReg, maskReg); ir.Write(IROp::Neg, shiftReg, shiftReg); ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); From 5c50c007b787426dc1ae0c9d88b2a2e5920fd8cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 3 Jan 2018 18:21:10 +0100 Subject: [PATCH 093/131] Minor fix for previous commit. --- GPU/Common/VertexDecoderCommon.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index e245917341..dccd4dccf9 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -559,7 +559,7 @@ void VertexDecoder::Step_Color5551Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] >= 255; } void VertexDecoder::Step_Color4444Morph() const @@ -575,7 +575,7 @@ void VertexDecoder::Step_Color4444Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] >= 255; } void VertexDecoder::Step_Color8888Morph() const @@ -591,7 +591,7 @@ void VertexDecoder::Step_Color8888Morph() const for (int i = 0; i < 4; i++) { c[i] = clamp_u8((int)col[i]); } - gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] == 255; + gstate_c.vertexFullAlpha = gstate_c.vertexFullAlpha && (int)col[3] >= 255; } void VertexDecoder::Step_NormalS8() const From cffb2d61a7c226952e5e83bb0225e71b6607f144 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 3 Jan 2018 23:19:44 -0800 Subject: [PATCH 094/131] irjit: Embed constant inside IRInst. This simplifies a bunch of code and improves compile performance by about 30%, at the cost of a bit more memory. --- Core/MIPS/IR/IRFrontend.cpp | 17 ++---- Core/MIPS/IR/IRFrontend.h | 2 +- Core/MIPS/IR/IRInst.cpp | 27 ++++----- Core/MIPS/IR/IRInst.h | 16 ++--- Core/MIPS/IR/IRInterpreter.cpp | 70 +++++++++++----------- Core/MIPS/IR/IRInterpreter.h | 2 +- Core/MIPS/IR/IRJit.cpp | 7 +-- Core/MIPS/IR/IRJit.h | 18 +----- Core/MIPS/IR/IRPassSimplify.cpp | 101 +++++--------------------------- 9 files changed, 80 insertions(+), 180 deletions(-) diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index fd602abc8d..bad7927b45 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -219,7 +219,7 @@ MIPSOpcode IRFrontend::GetOffsetInstruction(int offset) { return Memory::Read_Instruction(GetCompilerPC() + 4 * offset); } -void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::vector &constants, u32 &mipsBytes) { +void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes) { js.cancel = false; js.blockStart = em_address; js.compilerPC = em_address; @@ -244,12 +244,6 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v MIPSCompileOp(inst, this); js.compilerPC += 4; js.numInstructions++; - - if (ir.GetConstants().size() > 64) { - // Need to break the block - ir.Write(IROp::ExitToConst, ir.AddConstant(js.compilerPC)); - js.compiling = false; - } } mipsBytes = js.compilerPC - em_address; @@ -273,7 +267,6 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v } instructions = code->GetInstructions(); - constants = code->GetConstants(); if (logBlocks > 0 && dontLogBlocks == 0) { char temp2[256]; @@ -286,20 +279,20 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, std::v } if (logBlocks > 0 && dontLogBlocks == 0) { - NOTICE_LOG(JIT, "=============== Original IR (%d instructions, %d const) ===============", (int)ir.GetInstructions().size(), (int)ir.GetConstants().size()); + NOTICE_LOG(JIT, "=============== Original IR (%d instructions) ===============", (int)ir.GetInstructions().size()); for (size_t i = 0; i < ir.GetInstructions().size(); i++) { char buf[256]; - DisassembleIR(buf, sizeof(buf), ir.GetInstructions()[i], &ir.GetConstants()[0]); + DisassembleIR(buf, sizeof(buf), ir.GetInstructions()[i]); NOTICE_LOG(JIT, "%s", buf); } NOTICE_LOG(JIT, "=============== end ================="); } if (logBlocks > 0 && dontLogBlocks == 0) { - NOTICE_LOG(JIT, "=============== IR (%d instructions, %d const) ===============", (int)code->GetInstructions().size(), (int)code->GetConstants().size()); + NOTICE_LOG(JIT, "=============== IR (%d instructions) ===============", (int)code->GetInstructions().size()); for (size_t i = 0; i < code->GetInstructions().size(); i++) { char buf[256]; - DisassembleIR(buf, sizeof(buf), code->GetInstructions()[i], &code->GetConstants()[0]); + DisassembleIR(buf, sizeof(buf), code->GetInstructions()[i]); NOTICE_LOG(JIT, "%s", buf); } NOTICE_LOG(JIT, "=============== end ================="); diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index 600177be20..70194cef1b 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -88,7 +88,7 @@ public: void DoState(PointerWrap &p); bool CheckRounding(u32 blockAddress); // returns true if we need a do-over - void DoJit(u32 em_address, std::vector &instructions, std::vector &constants, u32 &mipsBytes); + void DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes); void EatPrefix() override { js.EatPrefix(); diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 655a736cee..fbdc25b5dd 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -174,7 +174,10 @@ void IRWriter::Write(IROp op, u8 dst, u8 src1, u8 src2) { inst.dest = dst; inst.src1 = src1; inst.src2 = src2; + inst.constant = nextConst_; insts_.push_back(inst); + + nextConst_ = 0; } void IRWriter::WriteSetConstant(u8 dst, u32 value) { @@ -182,16 +185,8 @@ void IRWriter::WriteSetConstant(u8 dst, u32 value) { } int IRWriter::AddConstant(u32 value) { - for (size_t i = 0; i < constPool_.size(); i++) { - if (constPool_[i] == value) - return (int)i; - } - constPool_.push_back(value); - if (constPool_.size() > 255) { - // Cannot have more than 256 constants in a block! - Crash(); - } - return (int)constPool_.size() - 1; + nextConst_ = value; + return 255; } int IRWriter::AddConstantFloat(float value) { @@ -215,7 +210,7 @@ const char *GetGPRName(int r) { } } -void DisassembleParam(char *buf, int bufSize, u8 param, char type, const u32 *constPool) { +void DisassembleParam(char *buf, int bufSize, u8 param, char type, u32 constant) { static const char *vfpuCtrlNames[VFPU_CTRL_MAX] = { "SPFX", "TPFX", @@ -271,7 +266,7 @@ void DisassembleParam(char *buf, int bufSize, u8 param, char type, const u32 *co } break; case 'C': - snprintf(buf, bufSize, "%08x", constPool[param]); + snprintf(buf, bufSize, "%08x", constant); break; case 'I': snprintf(buf, bufSize, "%02x", param); @@ -302,7 +297,7 @@ const IRMeta *GetIRMeta(IROp op) { return metaIndex[(int)op]; } -void DisassembleIR(char *buf, size_t bufsize, IRInst inst, const u32 *constPool) { +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); @@ -311,9 +306,9 @@ void DisassembleIR(char *buf, size_t bufsize, IRInst inst, const u32 *constPool) char bufDst[16]; char bufSrc1[16]; char bufSrc2[16]; - DisassembleParam(bufDst, sizeof(bufDst) - 2, inst.dest, meta->types[0], constPool); - DisassembleParam(bufSrc1, sizeof(bufSrc1) - 2, inst.src1, meta->types[1], constPool); - DisassembleParam(bufSrc2, sizeof(bufSrc2), inst.src2, meta->types[2], constPool); + 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); if (meta->types[1] && meta->types[0] != '_') { strcat(bufDst, ", "); } diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 4e8a5bcf61..30257c43e7 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -313,10 +313,7 @@ struct IRMeta { u32 flags; }; -// 32 bits. -// TODO: Evaluate whether it would make sense to switch to 64-bit ops with immediates -// included instead of storing immediates separately. Would simplify things at some memory -// storage and bandwidth cost. +// 64 bits. struct IRInst { IROp op; union { @@ -325,22 +322,21 @@ struct IRInst { }; u8 src1; u8 src2; + u32 constant; }; // Returns the new PC. -u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int count); +u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count); // Each IR block gets a constant pool. class IRWriter { public: IRWriter &operator =(const IRWriter &w) { insts_ = w.insts_; - constPool_ = w.constPool_; return *this; } IRWriter &operator =(IRWriter &&w) { insts_ = std::move(w.insts_); - constPool_ = std::move(w.constPool_); return *this; } @@ -355,15 +351,13 @@ public: void Clear() { insts_.clear(); - constPool_.clear(); } const std::vector &GetInstructions() const { return insts_; } - const std::vector &GetConstants() const { return constPool_; } private: std::vector insts_; - std::vector constPool_; + u32 nextConst_ = 0; }; struct IROptions { @@ -371,5 +365,5 @@ struct IROptions { }; const IRMeta *GetIRMeta(IROp op); -void DisassembleIR(char *buf, size_t bufsize, IRInst inst, const u32 *constPool); +void DisassembleIR(char *buf, size_t bufsize, IRInst inst); void InitIR(); diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index a245db1947..f1e8c348c8 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -58,7 +58,7 @@ u32 RunMemCheck(u32 pc, u32 addr) { return coreState != CORE_RUNNING ? 1 : 0; } -u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int count) { +u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { const IRInst *end = inst + count; while (inst != end) { switch (inst->op) { @@ -66,10 +66,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c _assert_(false); break; case IROp::SetConst: - mips->r[inst->dest] = constPool[inst->src1]; + mips->r[inst->dest] = inst->constant; break; case IROp::SetConstF: - memcpy(&mips->f[inst->dest], &constPool[inst->src1], 4); + memcpy(&mips->f[inst->dest], &inst->constant, 4); break; case IROp::Add: mips->r[inst->dest] = mips->r[inst->src1] + mips->r[inst->src2]; @@ -90,19 +90,19 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c mips->r[inst->dest] = mips->r[inst->src1]; break; case IROp::AddConst: - mips->r[inst->dest] = mips->r[inst->src1] + constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] + inst->constant; break; case IROp::SubConst: - mips->r[inst->dest] = mips->r[inst->src1] - constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] - inst->constant; break; case IROp::AndConst: - mips->r[inst->dest] = mips->r[inst->src1] & constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] & inst->constant; break; case IROp::OrConst: - mips->r[inst->dest] = mips->r[inst->src1] | constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] | inst->constant; break; case IROp::XorConst: - mips->r[inst->dest] = mips->r[inst->src1] ^ constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] ^ inst->constant; break; case IROp::Neg: mips->r[inst->dest] = -(s32)mips->r[inst->src1]; @@ -121,40 +121,40 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::Load8: - mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant); break; case IROp::Load8Ext: - mips->r[inst->dest] = (s32)(s8)Memory::ReadUnchecked_U8(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = (s32)(s8)Memory::ReadUnchecked_U8(mips->r[inst->src1] + inst->constant); break; case IROp::Load16: - mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant); break; case IROp::Load16Ext: - mips->r[inst->dest] = (s32)(s16)Memory::ReadUnchecked_U16(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = (s32)(s16)Memory::ReadUnchecked_U16(mips->r[inst->src1] + inst->constant); break; case IROp::Load32: - mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + constPool[inst->src2]); + mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant); break; case IROp::LoadFloat: - mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + constPool[inst->src2]); + mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant); break; case IROp::Store8: - Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_U8(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); break; case IROp::Store16: - Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_U16(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); break; case IROp::Store32: - Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); break; case IROp::StoreFloat: - Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + constPool[inst->src2]); + Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant); break; case IROp::LoadVec4: { - u32 base = mips->r[inst->src1] + constPool[inst->src2]; + u32 base = mips->r[inst->src1] + inst->constant; #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps((const float *)Memory::GetPointerUnchecked(base))); #else @@ -165,7 +165,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c } case IROp::StoreVec4: { - u32 base = mips->r[inst->src1] + constPool[inst->src2]; + u32 base = mips->r[inst->src1] + inst->constant; #if defined(_M_SSE) _mm_store_ps((float *)Memory::GetPointerUnchecked(base), _mm_load_ps(&mips->f[inst->dest])); #else @@ -474,11 +474,11 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::SltConst: - mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)constPool[inst->src2]; + mips->r[inst->dest] = (s32)mips->r[inst->src1] < (s32)inst->constant; break; case IROp::SltUConst: - mips->r[inst->dest] = mips->r[inst->src1] < constPool[inst->src2]; + mips->r[inst->dest] = mips->r[inst->src1] < inst->constant; break; case IROp::MovZ: @@ -731,34 +731,34 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::ExitToConst: - return constPool[inst->dest]; + return inst->constant; case IROp::ExitToReg: return mips->r[inst->src1]; case IROp::ExitToConstIfEq: if (mips->r[inst->src1] == mips->r[inst->src2]) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfNeq: if (mips->r[inst->src1] != mips->r[inst->src2]) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfGtZ: if ((s32)mips->r[inst->src1] > 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfGeZ: if ((s32)mips->r[inst->src1] >= 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfLtZ: if ((s32)mips->r[inst->src1] < 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::ExitToConstIfLeZ: if ((s32)mips->r[inst->src1] <= 0) - return constPool[inst->dest]; + return inst->constant; break; case IROp::Downcount: @@ -770,13 +770,13 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::SetPCConst: - mips->pc = constPool[inst->src1]; + mips->pc = inst->constant; break; case IROp::Syscall: // IROp::SetPC was (hopefully) executed before. { - MIPSOpcode op(constPool[inst->src1]); + MIPSOpcode op(inst->constant); CallSyscall(op); if (coreState != CORE_RUNNING) CoreTiming::ForceCheck(); @@ -788,14 +788,14 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c case IROp::Interpret: // SLOW fallback. Can be made faster. Ideally should be removed but may be useful for debugging. { - MIPSOpcode op(constPool[inst->src1]); + MIPSOpcode op(inst->constant); MIPSInterpret(op); break; } case IROp::CallReplacement: { - int funcIndex = constPool[inst->src1]; + int funcIndex = inst->constant; const ReplacementTableEntry *f = GetReplacementFunc(funcIndex); int cycles = f->replaceFunc(); mips->downcount -= cycles; @@ -810,7 +810,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c return mips->pc + 4; case IROp::SetCtrlVFPU: - mips->vfpuCtrl[inst->dest] = constPool[inst->src1]; + mips->vfpuCtrl[inst->dest] = inst->constant; break; case IROp::SetCtrlVFPUReg: @@ -829,7 +829,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int c break; case IROp::MemoryCheck: - if (RunMemCheck(mips->pc, mips->r[inst->src1] + constPool[inst->src2])) { + if (RunMemCheck(mips->pc, mips->r[inst->src1] + inst->constant)) { CoreTiming::ForceCheck(); return mips->pc; } diff --git a/Core/MIPS/IR/IRInterpreter.h b/Core/MIPS/IR/IRInterpreter.h index 562153673a..092c2ac936 100644 --- a/Core/MIPS/IR/IRInterpreter.h +++ b/Core/MIPS/IR/IRInterpreter.h @@ -20,4 +20,4 @@ inline static u32 ReverseBits32(u32 v) { return v; } -u32 IRInterpret(MIPSState *mips, const IRInst *inst, const u32 *constPool, int count); +u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count); diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 84db03b150..28d6ff411a 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -70,10 +70,9 @@ void IRJit::Compile(u32 em_address) { IRBlock *b = blocks_.GetBlock(block_num); std::vector instructions; - std::vector constants; u32 mipsBytes; - frontend_.DoJit(em_address, instructions, constants, mipsBytes); - b->SetInstructions(instructions, constants); + frontend_.DoJit(em_address, instructions, mipsBytes); + b->SetInstructions(instructions); b->SetOriginalSize(mipsBytes); // Overwrites the first instruction, and also updates stats. blocks_.FinalizeBlock(block_num); @@ -104,7 +103,7 @@ void IRJit::RunLoopUntil(u64 globalticks) { if (opcode == MIPS_EMUHACK_OPCODE) { u32 data = inst & 0xFFFFFF; IRBlock *block = blocks_.GetBlock(data); - mips_->pc = IRInterpret(mips_, block->GetInstructions(), block->GetConstants(), block->GetNumInstructions()); + mips_->pc = IRInterpret(mips_, block->GetInstructions(), block->GetNumInstructions()); } else { // RestoreRoundingMode(true); Compile(mips_->pc); diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 2e3f57a687..d9e0a7ed4d 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -38,40 +38,30 @@ namespace MIPSComp { // TODO : Use arena allocators. For now let's just malloc. class IRBlock { public: - IRBlock() : instr_(nullptr), const_(nullptr), numInstructions_(0), numConstants_(0), origAddr_(0), origSize_(0) {} - IRBlock(u32 emAddr) : instr_(nullptr), const_(nullptr), numInstructions_(0), numConstants_(0), origAddr_(emAddr), origSize_(0) {} + IRBlock() : instr_(nullptr), numInstructions_(0), origAddr_(0), origSize_(0) {} + IRBlock(u32 emAddr) : instr_(nullptr), numInstructions_(0), origAddr_(emAddr), origSize_(0) {} IRBlock(IRBlock &&b) { instr_ = b.instr_; - const_ = b.const_; numInstructions_ = b.numInstructions_; - numConstants_ = b.numConstants_; origAddr_ = b.origAddr_; origSize_ = b.origSize_; origFirstOpcode_ = b.origFirstOpcode_; b.instr_ = nullptr; - b.const_ = nullptr; } ~IRBlock() { delete[] instr_; - delete[] const_; } - void SetInstructions(const std::vector &inst, const std::vector &constants) { + void SetInstructions(const std::vector &inst) { instr_ = new IRInst[inst.size()]; numInstructions_ = (u16)inst.size(); if (!inst.empty()) { memcpy(instr_, &inst[0], sizeof(IRInst) * inst.size()); } - const_ = new u32[constants.size()]; - numConstants_ = (u16)constants.size(); - if (!constants.empty()) { - memcpy(const_, &constants[0], sizeof(u32) * constants.size()); - } } const IRInst *GetInstructions() const { return instr_; } - const u32 *GetConstants() const { return const_; } int GetNumInstructions() const { return numInstructions_; } MIPSOpcode GetOriginalFirstOp() const { return origFirstOpcode_; } bool HasOriginalFirstOp(); @@ -92,9 +82,7 @@ public: private: IRInst *instr_; - u32 *const_; u16 numInstructions_; - u16 numConstants_; u32 origAddr_; u32 origSize_; MIPSOpcode origFirstOpcode_; diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index c8e728f45c..f3798c1dfb 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -7,43 +7,6 @@ #include "Core/MIPS/IR/IRPassSimplify.h" #include "Core/MIPS/IR/IRRegCache.h" -void WriteInstWithConstants(const IRWriter &in, IRWriter &out, const u32 *constants, IRInst inst) { - // Remap constants to the new reality - const IRMeta *m = GetIRMeta(inst.op); - if (!m) { - ERROR_LOG(CPU, "Bad IR instruction %02x", (int)inst.op); - return; - } - switch (m->types[0]) { - case 'C': - if (!constants) { - ERROR_LOG(CPU, "Missing constant for type 0"); - return; - } - inst.dest = out.AddConstant(constants[inst.dest]); - break; - } - switch (m->types[1]) { - case 'C': - if (!constants) { - ERROR_LOG(CPU, "Missing constants for type 1"); - return; - } - inst.src1 = out.AddConstant(constants[inst.src1]); - break; - } - switch (m->types[2]) { - case 'C': - if (!constants) { - ERROR_LOG(CPU, "Missing constants for type 2"); - return; - } - inst.src2 = out.AddConstant(constants[inst.src2]); - break; - } - out.Write(inst); -} - u32 Evaluate(u32 a, u32 b, IROp op) { switch (op) { case IROp::Add: case IROp::AddConst: return a + b; @@ -137,13 +100,8 @@ bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWri } bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts) { - const u32 *constants = !in.GetConstants().empty() ? &in.GetConstants()[0] : nullptr; bool logBlocks = false; - IRInst prev; - prev.op = IROp::Nop; - prev.dest = 0; - prev.src1 = 0; - prev.src2 = 0; + IRInst prev{ IROp::Nop }; for (int i = 0; i < (int)in.GetInstructions().size(); i++) { IRInst inst = in.GetInstructions()[i]; switch (inst.op) { @@ -172,7 +130,7 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts) { // AddConst a0, sp, 0x30 // LoadVec4 v16, sp, 0x30 if (prev.op == IROp::AddConst && prev.dest == inst.src1 && prev.dest != prev.src1 && prev.src1 == MIPS_REG_SP) { - inst.src2 = out.AddConstant(constants[prev.src2] + constants[inst.src2]); + inst.constant += prev.constant; inst.src1 = prev.src1; logBlocks = 1; } else { @@ -182,7 +140,7 @@ bool OptimizeFPMoves(const IRWriter &in, IRWriter &out, const IROptions &opts) { break; */ default: - WriteInstWithConstants(in, out, constants, inst); + out.Write(inst); break; } prev = inst; @@ -238,28 +196,19 @@ bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts) { break; } } - // Can reuse the old constants array - not touching constants in this pass. - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } return logBlocks; } bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts) { IRRegCache gpr(&out); - const u32 *constants = !in.GetConstants().empty() ? &in.GetConstants()[0] : nullptr; bool logBlocks = false; for (int i = 0; i < (int)in.GetInstructions().size(); i++) { IRInst inst = in.GetInstructions()[i]; bool symmetric = true; - if (out.GetConstants().size() > 128) { - // Avoid causing a constant explosion. - goto doDefaultAndFlush; - } switch (inst.op) { case IROp::SetConst: - gpr.SetImm(inst.dest, constants[inst.src1]); + gpr.SetImm(inst.dest, inst.constant); break; case IROp::SetConstF: goto doDefault; @@ -328,7 +277,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::SltConst: case IROp::SltUConst: if (gpr.IsImm(inst.src1)) { - gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), constants[inst.src2], inst.op)); + gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), inst.constant, inst.op)); } else { gpr.MapDirtyIn(inst.dest, inst.src1); goto doDefault; @@ -428,7 +377,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::Store32: if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) { gpr.MapIn(inst.dest); - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapInIn(inst.dest, inst.src1); goto doDefault; @@ -437,7 +386,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::StoreFloat: case IROp::StoreVec4: if (gpr.IsImm(inst.src1)) { - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapIn(inst.src1); goto doDefault; @@ -451,7 +400,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::Load32: if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) { gpr.MapDirty(inst.dest); - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapDirtyIn(inst.dest, inst.src1); goto doDefault; @@ -460,7 +409,7 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::LoadFloat: case IROp::LoadVec4: if (gpr.IsImm(inst.src1)) { - out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + constants[inst.src2])); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); } else { gpr.MapIn(inst.src1); goto doDefault; @@ -581,10 +530,9 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::MemoryCheck: default: { - doDefaultAndFlush: gpr.FlushAll(); doDefault: - WriteInstWithConstants(in, out, constants, inst); + out.Write(inst); break; } } @@ -698,9 +646,6 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { } } - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } for (const IRInst &inst : insts) { if (inst.op != IROp::Mov || inst.dest != 0 || inst.src1 != 0) { out.Write(inst); @@ -711,10 +656,6 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { } bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } - // This tells us to skip an AND op that has been optimized out. // Maybe we could skip multiple, but that'd slow things down and is pretty uncommon. int nextSkip = -1; @@ -735,7 +676,7 @@ bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { } if (IRReadsFromGPR(laterInst, dest)) { if (IRDestGPR(laterInst) == dest && laterInst.op == IROp::AndConst) { - const u32 mask = in.GetConstants()[laterInst.src2]; + const u32 mask = laterInst.constant; // Here we are, maybe we can reduce the load size based on the mask. if ((mask & 0xffffff00) == 0) { inst.op = IROp::Load8; @@ -767,7 +708,7 @@ bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts) { return logBlocks; } -static std::vector ReorderLoadStoreOps(std::vector &ops, const u32 *consts) { +static std::vector ReorderLoadStoreOps(std::vector &ops) { if (ops.size() < 2) { return ops; } @@ -838,7 +779,7 @@ static std::vector ReorderLoadStoreOps(std::vector &ops, const u size_t end = j; if (start + 1 < end) { std::stable_sort(ops.begin() + start, ops.begin() + end, [&](const IRInst &a, const IRInst &b) { - return consts[a.src2] < consts[b.src2]; + return a.constant < b.constant; }); } } @@ -866,7 +807,7 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) } std::vector loadStoreUnsorted = loadStoreQueue; - std::vector loadStoreSorted = ReorderLoadStoreOps(loadStoreQueue, &in.GetConstants()[0]); + std::vector loadStoreSorted = ReorderLoadStoreOps(loadStoreQueue); if (memcmp(&loadStoreSorted[0], &loadStoreUnsorted[0], sizeof(IRInst) * loadStoreSorted.size()) != 0) { logBlocks = true; } @@ -1034,11 +975,6 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) break; } } - - // Can reuse the old constants array - not touching constants in this pass. - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } return logBlocks; } @@ -1050,8 +986,8 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { // Not similar enough at all. return false; } - u32 off1 = in.GetConstants()[a.src2]; - u32 off2 = in.GetConstants()[b.src2]; + u32 off1 = a.constant; + u32 off2 = b.constant; if (off1 + dist != off2) { // Not immediately sequential. return false; @@ -1172,10 +1108,5 @@ bool MergeLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) { break; } } - - // Can reuse the old constants array - not touching constants in this pass. - for (u32 value : in.GetConstants()) { - out.AddConstant(value); - } return logBlocks; } From bc541bd020b6408bacc3f8710f45e28df1c32df0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 3 Jan 2018 23:32:31 -0800 Subject: [PATCH 095/131] irjit: Encode downcount directly as a constant. Simpler this way, now. --- Core/MIPS/IR/IRCompBranch.cpp | 14 +++++++------- Core/MIPS/IR/IRFrontend.cpp | 6 +++--- Core/MIPS/IR/IRInst.cpp | 2 +- Core/MIPS/IR/IRInterpreter.cpp | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index 3a7affbd99..bb08883cc1 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -92,7 +92,7 @@ void IRFrontend::BranchRSRTComp(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); @@ -133,7 +133,7 @@ void IRFrontend::BranchRSZeroComp(MIPSOpcode op, IRComparison cc, bool andLink, CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); @@ -200,7 +200,7 @@ void IRFrontend::BranchFPFlag(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); @@ -249,7 +249,7 @@ void IRFrontend::BranchVFPUFlag(MIPSOpcode op, IRComparison cc, bool likely) { CompileDelaySlot(); int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; if (delaySlotIsBranch && (signed short)(delaySlotOp & 0xFFFF) != (signed short)(op & 0xFFFF) - 1) @@ -320,7 +320,7 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) { } int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; FlushAll(); @@ -384,7 +384,7 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { } int dcAmount = js.downcountAmount; - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; ir.Write(IROp::ExitToReg, 0, destReg, 0); @@ -397,7 +397,7 @@ void IRFrontend::Comp_JumpReg(MIPSOpcode op) { void IRFrontend::Comp_Syscall(MIPSOpcode op) { // Note: If we're in a delay slot, this is off by one compared to the interpreter. int dcAmount = js.downcountAmount + (js.inDelaySlot ? -1 : 0); - ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(dcAmount)); js.downcountAmount = 0; // If not in a delay slot, we need to update PC. diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index bad7927b45..8174fa17f2 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -162,7 +162,7 @@ void IRFrontend::Comp_ReplacementFunc(MIPSOpcode op) { MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this); } else { ApplyRoundingMode(); - ir.Write(IROp::Downcount, 0, js.downcountAmount & 0xFF, js.downcountAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(js.downcountAmount)); ir.Write(IROp::ExitToReg, 0, MIPS_REG_RA, 0); js.compiling = false; } @@ -319,7 +319,7 @@ void IRFrontend::CheckBreakpoint(u32 addr) { // TODO: In likely branches, downcount will be incorrect. int downcountOffset = js.inDelaySlot && js.downcountAmount >= 2 ? -2 : 0; int downcountAmount = js.downcountAmount + downcountOffset; - ir.Write(IROp::Downcount, 0, downcountAmount & 0xFF, downcountAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount)); // Note that this means downcount can't be metadata on the block. js.downcountAmount = -downcountOffset; ir.Write(IROp::Breakpoint); @@ -342,7 +342,7 @@ void IRFrontend::CheckMemoryBreakpoint(int rs, int offset) { downcountOffset = 0; } int downcountAmount = js.downcountAmount + downcountOffset; - ir.Write(IROp::Downcount, 0, downcountAmount & 0xFF, downcountAmount >> 8); + ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount)); // Note that this means downcount can't be metadata on the block. js.downcountAmount = -downcountOffset; ir.Write(IROp::MemoryCheck, 0, rs, ir.AddConstant(offset)); diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index fbdc25b5dd..2bd21d3fc3 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -141,7 +141,7 @@ static const IRMeta irMeta[] = { { IROp::Vec2Pack31To16, "Vec2Pack31To16", "2V" }, { IROp::Interpret, "Interpret", "_C" }, - { IROp::Downcount, "Downcount", "_II" }, + { IROp::Downcount, "Downcount", "_C" }, { IROp::ExitToPC, "ExitToPC", "", IRFLAG_EXIT }, { IROp::ExitToConst, "Exit", "C", IRFLAG_EXIT }, { IROp::ExitToConstIfEq, "ExitIfEq", "CGG", IRFLAG_EXIT }, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index f1e8c348c8..8de1fea2ec 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -762,7 +762,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { break; case IROp::Downcount: - mips->downcount -= (inst->src1) | ((inst->src2) << 8); + mips->downcount -= inst->constant; break; case IROp::SetPC: From 604b3c3e97c3384ce7d43e5ab243f31fae520012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 11:08:40 +0100 Subject: [PATCH 096/131] IR Interpreter: Add missing break; to switch case IROp::FSign. --- Core/MIPS/IR/IRInterpreter.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 8de1fea2ec..871cd08451 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -648,6 +648,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->f[inst->dest] = 1.0f; else mips->f[inst->dest] = -1.0f; + break; } case IROp::FpCondToReg: From 468b830bece1242b37ef1a532be884cda8cb58cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 12:22:49 +0100 Subject: [PATCH 097/131] Show IR disassembly in JIT Compare screen --- Core/MIPS/ARM/ArmJit.h | 1 + Core/MIPS/ARM64/Arm64Jit.h | 1 + Core/MIPS/IR/IRJit.cpp | 56 +++++++++++++++++++++++++-- Core/MIPS/IR/IRJit.h | 17 +++++--- Core/MIPS/JitCommon/JitBlockCache.cpp | 32 +++++++++++++-- Core/MIPS/JitCommon/JitBlockCache.h | 28 ++++++++++++-- Core/MIPS/JitCommon/JitCommon.h | 2 + Core/MIPS/MIPS/MipsJit.h | 1 + Core/MIPS/x86/Jit.h | 2 + UI/DevScreens.cpp | 42 +++++++++----------- 10 files changed, 141 insertions(+), 41 deletions(-) diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index 77b4c9ea51..903f43e094 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -168,6 +168,7 @@ public: int Replace_fabsf() override; JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } void RestoreSavedEmuHackOps(std::vector saved) override { blocks.RestoreSavedEmuHackOps(saved); } diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index 545de5846e..28fdb16501 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -169,6 +169,7 @@ public: int Replace_fabsf() override; JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } void RestoreSavedEmuHackOps(std::vector saved) override { blocks.RestoreSavedEmuHackOps(saved); } diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 28d6ff411a..868c4b6533 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -116,7 +116,7 @@ void IRJit::RunLoopUntil(u64 globalticks) { } bool IRJit::DescribeCodePtr(const u8 *ptr, std::string &name) { - // Used in disassembly viewer. + // Used in target disassembly viewer. return false; } @@ -174,7 +174,7 @@ void IRBlockCache::FinalizeBlock(int i) { } } -u32 IRBlockCache::AddressToPage(u32 addr) { +u32 IRBlockCache::AddressToPage(u32 addr) const { // Use relatively small pages since basic blocks are typically small. return (addr & 0x3FFFFFFF) >> 10; } @@ -210,7 +210,55 @@ void IRBlockCache::RestoreSavedEmuHackOps(std::vector saved) { } } -bool IRBlock::HasOriginalFirstOp() { +JitBlockDebugInfo IRBlockCache::GetBlockDebugInfo(int blockNum) const { + const IRBlock &ir = blocks_[blockNum]; + JitBlockDebugInfo debugInfo{}; + uint32_t start, size; + ir.GetRange(start, size); + debugInfo.originalAddress = start; // TODO + + for (u32 addr = start; addr < start + size; addr += 4) { + char temp[256]; + MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, true); + std::string mipsDis = temp; + debugInfo.origDisasm.push_back(mipsDis); + } + + for (int i = 0; i < ir.GetNumInstructions(); i++) { + IRInst inst = ir.GetInstructions()[i]; + char buffer[256]; + DisassembleIR(buffer, sizeof(buffer), inst); + debugInfo.irDisasm.push_back(buffer); + } + return debugInfo; +} + +void IRBlockCache::ComputeStats(BlockCacheStats &bcStats) const { + // TODO: Implement properly + memset(&bcStats, 0, sizeof(bcStats)); + bcStats.numBlocks = (int)blocks_.size(); +} + +int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const { + u32 startPage = AddressToPage(em_address); + u32 endPage = AddressToPage(em_address + 4); + + for (u32 page = startPage; page <= endPage; ++page) { + const auto iter = byPage_.find(page); + if (iter == byPage_.end()) + continue; + + const std::vector &blocksInPage = iter->second; + for (int i : blocksInPage) { + if (blocks_[i].OverlapsRange(em_address, 4)) { + return i; + } + } + } + return -1; +} + +bool IRBlock::HasOriginalFirstOp() const { return Memory::ReadUnchecked_U32(origAddr_) == origFirstOpcode_.encoding; } @@ -240,7 +288,7 @@ void IRBlock::Destroy(int number) { } } -bool IRBlock::OverlapsRange(u32 addr, u32 size) { +bool IRBlock::OverlapsRange(u32 addr, u32 size) const { addr &= 0x3FFFFFFF; u32 origAddr = origAddr_ & 0x3FFFFFFF; return addr + size > origAddr && addr < origAddr + origSize_; diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index d9e0a7ed4d..7e3055c253 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -64,15 +64,15 @@ public: const IRInst *GetInstructions() const { return instr_; } int GetNumInstructions() const { return numInstructions_; } MIPSOpcode GetOriginalFirstOp() const { return origFirstOpcode_; } - bool HasOriginalFirstOp(); + bool HasOriginalFirstOp() const; bool RestoreOriginalFirstOp(int number); bool IsValid() const { return origAddr_ != 0; } void SetOriginalSize(u32 size) { origSize_ = size; } - bool OverlapsRange(u32 addr, u32 size); + bool OverlapsRange(u32 addr, u32 size) const; - void GetRange(u32 &start, u32 &size) { + void GetRange(u32 &start, u32 &size) const { start = origAddr_; size = origSize_; } @@ -88,13 +88,13 @@ private: MIPSOpcode origFirstOpcode_; }; -class IRBlockCache { +class IRBlockCache : public JitBlockCacheDebugInterface { public: IRBlockCache() {} void Clear(); void InvalidateICache(u32 address, u32 length); void FinalizeBlock(int i); - int GetNumBlocks() const { return (int)blocks_.size(); } + int GetNumBlocks() const override { return (int)blocks_.size(); } int AllocateBlock(int emAddr) { blocks_.push_back(IRBlock(emAddr)); return (int)blocks_.size() - 1; @@ -110,8 +110,12 @@ public: std::vector SaveAndClearEmuHackOps(); void RestoreSavedEmuHackOps(std::vector saved); + JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const override; + void ComputeStats(BlockCacheStats &bcStats) const override; + int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const override; + private: - u32 AddressToPage(u32 addr); + u32 AddressToPage(u32 addr) const; std::vector blocks_; std::unordered_map> byPage_; @@ -133,6 +137,7 @@ public: bool DescribeCodePtr(const u8 *ptr, std::string &name) override; // Not using a regular block cache. JitBlockCache *GetBlockCache() override { return nullptr; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks_; } MIPSOpcode GetOriginalOp(MIPSOpcode op) override; std::vector SaveAndClearEmuHackOps() override { return blocks_.SaveAndClearEmuHackOps(); } diff --git a/Core/MIPS/JitCommon/JitBlockCache.cpp b/Core/MIPS/JitCommon/JitBlockCache.cpp index c7f6e5a346..6958fc894a 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.cpp +++ b/Core/MIPS/JitCommon/JitBlockCache.cpp @@ -123,6 +123,10 @@ JitBlock *JitBlockCache::GetBlock(int no) { return &blocks_[no]; } +const JitBlock *JitBlockCache::GetBlock(int no) const { + return &blocks_[no]; +} + int JitBlockCache::AllocateBlock(u32 startAddress) { JitBlock &b = blocks_[num_blocks_]; @@ -326,7 +330,7 @@ MIPSOpcode JitBlockCache::GetEmuHackOpForBlock(int blockNum) const { return MIPSOpcode(MIPS_EMUHACK_OPCODE | off); } -int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr, bool realBlocksOnly) { +int JitBlockCache::GetBlockNumberFromStartAddress(u32 addr, bool realBlocksOnly) const { if (!blocks_ || !Memory::IsValidAddress(addr)) return -1; @@ -610,12 +614,12 @@ int JitBlockCache::GetBlockExitSize() { #endif } -void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) { +void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) const { double totalBloat = 0.0; double maxBloat = 0.0; double minBloat = 1000000000.0; for (int i = 0; i < num_blocks_; i++) { - JitBlock *b = GetBlock(i); + const JitBlock *b = GetBlock(i); double codeSize = (double)b->codeSize; if (codeSize == 0) continue; @@ -637,3 +641,25 @@ void JitBlockCache::ComputeStats(BlockCacheStats &bcStats) { bcStats.maxBloat = maxBloat; bcStats.avgBloat = totalBloat / (double)num_blocks_; } + +JitBlockDebugInfo JitBlockCache::GetBlockDebugInfo(int blockNum) const { + JitBlockDebugInfo debugInfo{}; + const JitBlock *block = GetBlock(blockNum); + debugInfo.originalAddress = block->originalAddress; + for (u32 addr = block->originalAddress; addr <= block->originalAddress + block->originalSize * 4; addr += 4) { + char temp[256]; + MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, true); + std::string mipsDis = temp; + debugInfo.origDisasm.push_back(mipsDis); + } + +#if defined(ARM) + debugInfo.targetDisasm = DisassembleArm2(block->normalEntry, block->codeSize); +#elif defined(ARM64) + debugInfo.targetDisasm = DisassembleArm64(block->normalEntry, block->codeSize); +#elif defined(_M_IX86) || defined(_M_X64) + debugInfo.targetDisasm = DisassembleX86(block->normalEntry, block->codeSize); +#endif + + return debugInfo; +} diff --git a/Core/MIPS/JitCommon/JitBlockCache.h b/Core/MIPS/JitCommon/JitBlockCache.h index 6e01b80ad1..ce609b8ada 100644 --- a/Core/MIPS/JitCommon/JitBlockCache.h +++ b/Core/MIPS/JitCommon/JitBlockCache.h @@ -93,7 +93,24 @@ struct JitBlock { typedef void (*CompiledCode)(); -class JitBlockCache { +struct JitBlockDebugInfo { + uint32_t originalAddress; + std::vector origDisasm; + std::vector irDisasm; // if any + std::vector targetDisasm; +}; + +class JitBlockCacheDebugInterface { +public: + virtual int GetNumBlocks() const = 0; + virtual int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const = 0; + virtual JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const = 0; + virtual void ComputeStats(BlockCacheStats &bcStats) const = 0; + + virtual ~JitBlockCacheDebugInterface() {} +}; + +class JitBlockCache : public JitBlockCacheDebugInterface { public: JitBlockCache(MIPSState *mips_, CodeBlockCommon *codeBlock); ~JitBlockCache(); @@ -109,13 +126,14 @@ public: void Reset(); bool IsFull() const; - void ComputeStats(BlockCacheStats &bcStats); + void ComputeStats(BlockCacheStats &bcStats) const override; // Code Cache JitBlock *GetBlock(int block_num); + const JitBlock *GetBlock(int block_num) const; // Fast way to get a block. Only works on the first source-cpu instruction of a block. - int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true); + int GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly = true) const override; // slower, but can get numbers from within blocks, not just the first instruction. // WARNING! WILL NOT WORK WITH JIT INLINING ENABLED (not yet a feature but will be soon) @@ -140,10 +158,12 @@ public: std::vector SaveAndClearEmuHackOps(); void RestoreSavedEmuHackOps(std::vector saved); - int GetNumBlocks() const { return num_blocks_; } + int GetNumBlocks() const override { return num_blocks_; } static int GetBlockExitSize(); + JitBlockDebugInfo GetBlockDebugInfo(int blockNum) const override; + enum { MAX_BLOCK_INSTRUCTIONS = 0x4000, }; diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index 6296506a01..74341169b5 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -31,6 +31,7 @@ std::vector DisassembleX86(const u8 *data, int size); struct JitBlock; class JitBlockCache; +class JitBlockCacheDebugInterface; class PointerWrap; #ifdef USING_QT_UI @@ -123,6 +124,7 @@ namespace MIPSComp { virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0; virtual const u8 *GetDispatcher() const = 0; virtual JitBlockCache *GetBlockCache() = 0; + virtual JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() = 0; virtual void InvalidateCacheAt(u32 em_address, int length = 4) = 0; virtual void DoState(PointerWrap &p) = 0; virtual void RunLoopUntil(u64 globalticks) = 0; diff --git a/Core/MIPS/MIPS/MipsJit.h b/Core/MIPS/MIPS/MipsJit.h index c0ff28582d..bc6a526f01 100644 --- a/Core/MIPS/MIPS/MipsJit.h +++ b/Core/MIPS/MIPS/MipsJit.h @@ -129,6 +129,7 @@ public: void Comp_Vbfy(MIPSOpcode op) {} JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } void RestoreSavedEmuHackOps(std::vector saved) override { blocks.RestoreSavedEmuHackOps(saved); } diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index 2a35212140..d6ff79b003 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -158,6 +158,8 @@ public: void UpdateRoundingMode(); JitBlockCache *GetBlockCache() override { return &blocks; } + JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() override { return &blocks; } + MIPSOpcode GetOriginalOp(MIPSOpcode op) override; std::vector SaveAndClearEmuHackOps() override { return blocks.SaveAndClearEmuHackOps(); } diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 6281d6f99e..542a65a225 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -701,46 +701,41 @@ void JitCompareScreen::UpdateDisasm() { I18NCategory *dev = GetI18NCategory("Developer"); - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCacheDebug = MIPSComp::jit->GetBlockCacheDebugInterface(); char temp[256]; - snprintf(temp, sizeof(temp), "%i/%i", currentBlock_, blockCache->GetNumBlocks()); + snprintf(temp, sizeof(temp), "%i/%i", currentBlock_, blockCacheDebug->GetNumBlocks()); blockName_->SetText(temp); - if (currentBlock_ < 0 || currentBlock_ >= blockCache->GetNumBlocks()) { + if (currentBlock_ < 0 || currentBlock_ >= blockCacheDebug->GetNumBlocks()) { leftDisasm_->Add(new TextView(dev->T("No block"))); rightDisasm_->Add(new TextView(dev->T("No block"))); blockStats_->SetText(""); return; } - JitBlock *block = blockCache->GetBlock(currentBlock_); + JitBlockDebugInfo debugInfo = blockCacheDebug->GetBlockDebugInfo(currentBlock_); - snprintf(temp, sizeof(temp), "%08x", block->originalAddress); + snprintf(temp, sizeof(temp), "%08x", debugInfo.originalAddress); blockAddr_->SetText(temp); // Alright. First generate the MIPS disassembly. // TODO: Need a way to communicate branch continuing. - for (u32 addr = block->originalAddress; addr <= block->originalAddress + block->originalSize * 4; addr += 4) { - char temp[256]; - MIPSDisAsm(Memory::Read_Instruction(addr), addr, temp, true); - std::string mipsDis = temp; - leftDisasm_->Add(new TextView(mipsDis))->SetFocusable(true); + for (auto line : debugInfo.origDisasm) { + leftDisasm_->Add(new TextView(line))->SetFocusable(true); } -#if defined(ARM) - std::vector targetDis = DisassembleArm2(block->normalEntry, block->codeSize); -#elif defined(ARM64) - std::vector targetDis = DisassembleArm64(block->normalEntry, block->codeSize); -#elif defined(_M_IX86) || defined(_M_X64) - std::vector targetDis = DisassembleX86(block->normalEntry, block->codeSize); -#endif -#if defined(ARM) || defined(ARM64) || defined(_M_IX86) || defined(_M_X64) - for (size_t i = 0; i < targetDis.size(); i++) { - rightDisasm_->Add(new TextView(targetDis[i]))->SetFocusable(true); + // TODO : When we have both target and IR, need a third column. + if (debugInfo.targetDisasm.size()) { + for (auto line : debugInfo.targetDisasm) { + rightDisasm_->Add(new TextView(line))->SetFocusable(true); + } + } else { + for (auto line : debugInfo.irDisasm) { + rightDisasm_->Add(new TextView(line))->SetFocusable(true); + } } -#endif int numMips = leftDisasm_->GetNumSubviews(); int numHost = rightDisasm_->GetNumSubviews(); @@ -773,7 +768,7 @@ UI::EventReturn JitCompareScreen::OnShowStats(UI::EventParams &e) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); BlockCacheStats bcStats; blockCache->ComputeStats(bcStats); NOTICE_LOG(JIT, "Num blocks: %i", bcStats.numBlocks); @@ -790,7 +785,6 @@ UI::EventReturn JitCompareScreen::OnShowStats(UI::EventParams &e) { } ctr++; } - return UI::EVENT_DONE; } @@ -839,7 +833,7 @@ UI::EventReturn JitCompareScreen::OnRandomBlock(UI::EventParams &e) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return UI::EVENT_DONE; From 1a97f62dc9c2c989a38b0a67b9c2d3505160fd64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 18:10:41 +0100 Subject: [PATCH 098/131] Fix running the CPU test from the UI. --- Core/MIPS/IR/IRCompVFPU.cpp | 1 - Core/System.cpp | 4 +++- UI/GameSettingsScreen.cpp | 9 +------- android/jni/TestRunner.cpp | 41 ++++++++++++++++++++++++++++--------- android/jni/TestRunner.h | 3 ++- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 8638abae0b..af89d6cb47 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -333,7 +333,6 @@ namespace MIPSComp { case 53: // lvl/lvr.q - highly unusual case 61: // svl/svr.q - highly unusual - logBlocks = 1; DISABLE; break; diff --git a/Core/System.cpp b/Core/System.cpp index 0e223ad79d..cc41c39313 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -29,6 +29,7 @@ #include #include +#include "base/timeutil.h" #include "math/math_util.h" #include "thread/threadutil.h" #include "util/text/utf8.h" @@ -465,7 +466,8 @@ bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) { CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); } - PSP_InitUpdate(error_string); + while (!PSP_InitUpdate(error_string)) + sleep_ms(10); return pspIsInited; } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index bef66c4c0f..34fec613b6 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1219,14 +1219,7 @@ void DeveloperToolsScreen::CreateViews() { Choice *cpuTests = new Choice(dev->T("Run CPU Tests")); list->Add(cpuTests)->OnClick.Handle(this, &DeveloperToolsScreen::OnRunCPUTests); -#ifdef IOS - const std::string testDirectory = g_Config.flash0Directory + "../"; -#else - const std::string testDirectory = g_Config.memStickDirectory; -#endif - if (!File::Exists(testDirectory + "pspautotests/tests/")) { - cpuTests->SetEnabled(false); - } + cpuTests->SetEnabled(TestsAvailable()); #endif list->Add(new CheckBox(&g_Config.bEnableLogging, dev->T("Enable Logging")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoggingChanged); diff --git a/android/jni/TestRunner.cpp b/android/jni/TestRunner.cpp index 29ca2b2a0c..c58aa8e9e4 100644 --- a/android/jni/TestRunner.cpp +++ b/android/jni/TestRunner.cpp @@ -25,11 +25,12 @@ #include #include +#include "ppsspp_config.h" #include "base/basictypes.h" #include "base/display.h" #include "base/logging.h" -#include "gfx/gl_common.h" +#include "Common/FileUtil.h" #include "Core/Core.h" #include "Core/System.h" #include "Core/Config.h" @@ -58,21 +59,37 @@ static std::string TrimNewlines(const std::string &s) { return s.substr(0, p + 1); } -void RunTests() -{ +bool TestsAvailable() { +#ifdef IOS + std::string testDirectory = g_Config.flash0Directory + "../"; +#else + std::string testDirectory = g_Config.memStickDirectory; +#endif + // Hack to easily run the tests on Windows from the submodule + if (File::IsDirectory("../pspautotests")) { + testDirectory = "../"; + } + return File::Exists(testDirectory + "pspautotests/tests/"); +} + +bool RunTests() { std::string output; #ifdef IOS - const std::string baseDirectory = g_Config.flash0Directory + "../"; + std::string baseDirectory = g_Config.flash0Directory + "../"; #else - const std::string baseDirectory = g_Config.memStickDirectory; + std::string baseDirectory = g_Config.memStickDirectory; + // Hack to easily run the tests on Windows from the submodule + if (File::IsDirectory("../pspautotests")) { + baseDirectory = "../"; + } #endif CoreParameter coreParam; coreParam.cpuCore = (CPUCore)g_Config.iCpuCore; - coreParam.gpuCore = g_Config.bSoftwareRendering ? GPUCORE_SOFTWARE : GPUCORE_GLES; + coreParam.gpuCore = GPUCORE_NULL; coreParam.enableSound = g_Config.bEnableSound; - coreParam.graphicsContext = PSP_CoreParameter().graphicsContext; + coreParam.graphicsContext = nullptr; coreParam.mountIso = ""; coreParam.mountRoot = baseDirectory + "pspautotests/"; coreParam.startPaused = false; @@ -95,17 +112,20 @@ void RunTests() coreParam.fileToStart = baseDirectory + "pspautotests/tests/" + testName + ".prx"; std::string expectedFile = baseDirectory + "pspautotests/tests/" + testName + ".expected"; - ILOG("Preparing to execute %s", testName); + ILOG("Preparing to execute '%s'", testName); std::string error_string; output = ""; if (!PSP_Init(coreParam, &error_string)) { ELOG("Failed to init unittest %s : %s", testsToRun[i], error_string.c_str()); PSP_CoreParameter().pixelWidth = pixel_xres; PSP_CoreParameter().pixelHeight = pixel_yres; - return; + return false; } + PSP_BeginHostFrame(); + // Run the emu until the test exits + ILOG("Test: Entering runloop."); while (true) { int blockTicks = usToCycles(1000000 / 10); while (coreState == CORE_RUNNING) { @@ -120,6 +140,7 @@ void RunTests() break; } } + PSP_EndHostFrame(); std::ifstream expected(expectedFile.c_str(), std::ios_base::in); if (!expected) { @@ -152,9 +173,9 @@ void RunTests() } PSP_Shutdown(); } - glViewport(0,0,pixel_xres,pixel_yres); PSP_CoreParameter().pixelWidth = pixel_xres; PSP_CoreParameter().pixelHeight = pixel_yres; PSP_CoreParameter().headLess = false; g_Config.sReportHost = savedReportHost; + return true; // Managed to execute the tests. Says nothing about the result. } diff --git a/android/jni/TestRunner.h b/android/jni/TestRunner.h index 50f4546a6f..b7bd7f53c8 100644 --- a/android/jni/TestRunner.h +++ b/android/jni/TestRunner.h @@ -17,4 +17,5 @@ #pragma once -void RunTests(); +bool RunTests(); +bool TestsAvailable(); From a128624f986939fefccfaa7e37ba3b5016d14111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 18:25:54 +0100 Subject: [PATCH 099/131] IRInterpreter: Fix bugs in floating point truncation functions --- Core/MIPS/IR/IRInterpreter.cpp | 57 +++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 871cd08451..d5ad7be82b 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -658,29 +658,58 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->r[inst->dest] = mips->vfpuCtrl[inst->src1]; break; case IROp::FRound: - mips->fs[inst->dest] = (int)floorf(mips->f[inst->src1] + 0.5f); - break; - case IROp::FTrunc: { - float src = mips->f[inst->src1]; - if (src >= 0.0f) { - mips->fs[inst->dest] = (int)floorf(src); - // Overflow, but it was positive. - if (mips->fs[inst->dest] == -2147483648LL) { - mips->fs[inst->dest] = 2147483647LL; - } + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; } else { - // Overflow happens to be the right value anyway. - mips->fs[inst->dest] = (int)ceilf(src); + mips->fs[inst->dest] = (int)floorf(value + 0.5f); } break; } + case IROp::FTrunc: + { + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; + } else { + if (value >= 0.0f) { + mips->fs[inst->dest] = (int)floorf(value); + // Overflow, but it was positive. + if (mips->fs[inst->dest] == -2147483648LL) { + mips->fs[inst->dest] = 2147483647LL; + } + } else { + // Overflow happens to be the right value anyway. + mips->fs[inst->dest] = (int)ceilf(value); + } + break; + } + } case IROp::FCeil: - mips->fs[inst->dest] = (int)ceilf(mips->f[inst->src1]); + { + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; + } else { + mips->fs[inst->dest] = (int)ceilf(value); + } break; + } case IROp::FFloor: - mips->fs[inst->dest] = (int)floorf(mips->f[inst->src1]); + { + float value = mips->f[inst->src1]; + if (my_isnanorinf(value)) { + mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL; + break; + } else { + mips->fs[inst->dest] = (int)floorf(value); + } break; + } case IROp::FCmp: switch (inst->dest) { case IRFpCompareMode::False: From e0cc126d09390522ae30b5b8413e3490f00a4258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 11:37:32 +0100 Subject: [PATCH 100/131] Add some more SIMD support to IR interpreter. Mostly just because, but also serves as implementation reference for later code generation backends. --- Core/MIPS/IR/IRInterpreter.cpp | 50 +++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index d5ad7be82b..8d1b67287f 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -1,11 +1,16 @@ #include #include +#include "ppsspp_config.h" #include "math/math_util.h" #include "Common/Common.h" #ifdef _M_SSE -#include +#include +#endif + +#if PPSSPP_ARCH(ARM_NEON) +#include #endif #include "Core/Core.h" @@ -40,6 +45,10 @@ alignas(16) static const uint32_t noSignMask[4] = { 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, 0x7FFFFFFF, }; +alignas(16) static const uint32_t lowBytesMask[4] = { + 0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF, +}; + u32 RunBreakpoint(u32 pc) { // Should we skip this breakpoint? if (CBreakPoints::CheckSkipFirst() == pc) @@ -58,6 +67,7 @@ u32 RunMemCheck(u32 pc, u32 addr) { return coreState != CORE_RUNNING ? 1 : 0; } +// We cannot use NEON on ARM32 here until we make it a hard dependency. We can, however, on ARM64. u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { const IRInst *end = inst + count; while (inst != end) { @@ -185,8 +195,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Vec4Shuffle: { - // Can't use the SSE shuffle here because it takes an immediate. - // Backends with SSE support could use that though. + // Can't use the SSE shuffle here because it takes an immediate. pshufb with a table would work though, + // or a big switch - there are only 256 shuffles possible (4^4) for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + ((inst->src2 >> (i * 2)) & 3)]; break; @@ -195,6 +205,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Vec4Mov: #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps(&mips->f[inst->src1])); +#elif PPSSPP_CONFIG(ARM64) + float32x4_t c = vld1q_f32(&mips->f[inst->src1]); + vst1q_f32(&mips->f[inst->dest], c); #else memcpy(&mips->f[inst->dest], &mips->f[inst->src1], 4 * sizeof(float)); #endif @@ -274,10 +287,17 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { break; case IROp::Vec4Unpack8To32: +#if defined(_M_SSE) + __m128i src = _mm_cvtsi32_si128(mips->fi[inst->src1]); + src = _mm_unpacklo_epi16(src, _mm_setzero_si128()); + src = _mm_unpacklo_epi32(src, _mm_setzero_si128()); + _mm_store_si128((__m128i *)&mips->fi[inst->dest], _mm_slli_epi32(src, 24)); +#else mips->fi[inst->dest] = (mips->fi[inst->src1] << 24); mips->fi[inst->dest + 1] = (mips->fi[inst->src1] << 16) & 0xFF000000; mips->fi[inst->dest + 2] = (mips->fi[inst->src1] << 8) & 0xFF000000; mips->fi[inst->dest + 3] = (mips->fi[inst->src1]) & 0xFF000000; +#endif break; case IROp::Vec2Pack32To16: @@ -297,21 +317,36 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Vec4Pack32To8: { +#if defined(_M_SSE) + // Packs the upper bits, so we need to shift down. Then we can just use SSE packing. + __m128i val = _mm_srli_epi32(_mm_load_si128((const __m128i *)&mips->fi[inst->src1]), 24); + val = _mm_packs_epi16(_mm_packs_epi32(val, _mm_setzero_si128()), _mm_setzero_si128()); + mips->fi[inst->dest] = _mm_cvtsi128_si32(val); +#else u32 val = mips->fi[inst->src1] >> 24; val |= (mips->fi[inst->src1 + 1] >> 16) & 0xFF00; val |= (mips->fi[inst->src1 + 2] >> 8) & 0xFF0000; val |= (mips->fi[inst->src1 + 3]) & 0xFF000000; mips->fi[inst->dest] = val; break; +#endif } case IROp::Vec4Pack31To8: { +#if defined(_M_SSE) + // Packs the upper bits (offset by 1), so we need to shift down and mask. Then we can just use SSE packing. + __m128i val = _mm_srli_epi32(_mm_load_si128((const __m128i *)&mips->fi[inst->src1]), 23); + val = _mm_and_si128(val, _mm_load_si128((const __m128i *)&lowBytesMask)); + val = _mm_packs_epi16(_mm_packs_epi32(val, _mm_setzero_si128()), _mm_setzero_si128()); + mips->fi[inst->dest] = _mm_cvtsi128_si32(val); +#else u32 val = (mips->fi[inst->src1] >> 23) & 0xFF; val |= (mips->fi[inst->src1 + 1] >> 15) & 0xFF00; val |= (mips->fi[inst->src1 + 2] >> 7) & 0xFF0000; val |= (mips->fi[inst->src1 + 3] << 1) & 0xFF000000; mips->fi[inst->dest] = val; +#endif break; } @@ -326,14 +361,21 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Vec4ClampToZero: { +#if 0 && defined(_M_SSE) + // This is SSE4 only unfortunately, so only suitable for JIT, hence disabled above. + __m128i val = _mm_load_si128((const __m128i *)&mips->fi[inst->src1]); + val = _mm_max_epi32(val, _mm_setzero_si128()); + mips->fi[inst->dest] = _mm_cvtsi128_si32(val); +#else for (int i = 0; i < 4; i++) { u32 val = mips->fi[inst->src1 + i]; mips->fi[inst->dest + i] = (int)val >= 0 ? val : 0; } break; +#endif } - case IROp::Vec4DuplicateUpperBitsAndShift1: + case IROp::Vec4DuplicateUpperBitsAndShift1: // For vuc2i, the weird one. for (int i = 0; i < 4; i++) { u32 val = mips->fi[inst->src1 + i]; val = val | (val >> 8); From fe88d12055ef2c8f3a0f953dc3573cf5860c6dae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 13:40:39 +0100 Subject: [PATCH 101/131] IR interpreter: Add some braces to allow variable declaration in the switch cases. --- Core/MIPS/IR/IRInterpreter.cpp | 41 +++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 8d1b67287f..e19be9027a 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -186,12 +186,14 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { } case IROp::Vec4Init: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps(vec4InitValues[inst->src1])); #else memcpy(&mips->f[inst->dest], vec4InitValues[inst->src1], 4 * sizeof(float)); #endif break; + } case IROp::Vec4Shuffle: { @@ -203,44 +205,58 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { } case IROp::Vec4Mov: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_load_ps(&mips->f[inst->src1])); -#elif PPSSPP_CONFIG(ARM64) - float32x4_t c = vld1q_f32(&mips->f[inst->src1]); - vst1q_f32(&mips->f[inst->dest], c); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vld1q_f32(&mips->f[inst->src1])); #else memcpy(&mips->f[inst->dest], &mips->f[inst->src1], 4 * sizeof(float)); #endif break; + } case IROp::Vec4Add: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_add_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vaddq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] + mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Sub: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_sub_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vsubq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] - mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Mul: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vmulq_f32(vld1q_f32(&mips->f[inst->src1]), vld1q_f32(&mips->f[inst->src2]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = mips->f[inst->src1 + i] * mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Div: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_div_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps(&mips->f[inst->src2]))); #else @@ -248,8 +264,10 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->f[inst->dest + i] = mips->f[inst->src1 + i] / mips->f[inst->src2 + i]; #endif break; + } case IROp::Vec4Scale: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_mul_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_set1_ps(mips->f[inst->src2]))); #else @@ -257,36 +275,50 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->f[inst->dest + i] = mips->f[inst->src1 + i] * mips->f[inst->src2]; #endif break; + } case IROp::Vec4Neg: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_xor_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)signBits))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vnegq_f32(vld1q_f32(&mips->f[inst->src1]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = -mips->f[inst->src1 + i]; #endif break; + } case IROp::Vec4Abs: + { #if defined(_M_SSE) _mm_store_ps(&mips->f[inst->dest], _mm_and_ps(_mm_load_ps(&mips->f[inst->src1]), _mm_load_ps((const float *)noSignMask))); +#elif PPSSPP_ARCH(ARM64) + vst1q_f32(&mips->f[inst->dest], vabsq_f32(vld1q_f32(&mips->f[inst->src1]))); #else for (int i = 0; i < 4; i++) mips->f[inst->dest + i] = fabsf(mips->f[inst->src1 + i]); #endif break; + } case IROp::Vec2Unpack16To31: + { mips->fi[inst->dest] = (mips->fi[inst->src1] << 16) >> 1; mips->fi[inst->dest + 1] = (mips->fi[inst->src1] & 0xFFFF0000) >> 1; break; + } case IROp::Vec2Unpack16To32: + { mips->fi[inst->dest] = (mips->fi[inst->src1] << 16); mips->fi[inst->dest + 1] = (mips->fi[inst->src1] & 0xFFFF0000); break; + } case IROp::Vec4Unpack8To32: + { #if defined(_M_SSE) __m128i src = _mm_cvtsi32_si128(mips->fi[inst->src1]); src = _mm_unpacklo_epi16(src, _mm_setzero_si128()); @@ -299,6 +331,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->fi[inst->dest + 3] = (mips->fi[inst->src1]) & 0xFF000000; #endif break; + } case IROp::Vec2Pack32To16: { @@ -376,6 +409,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { } case IROp::Vec4DuplicateUpperBitsAndShift1: // For vuc2i, the weird one. + { for (int i = 0; i < 4; i++) { u32 val = mips->fi[inst->src1 + i]; val = val | (val >> 8); @@ -384,6 +418,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { mips->fi[inst->dest + i] = val; } break; + } case IROp::FCmpVfpuBit: { From ca9050b84c655ca0e3c5e6b5038201a8bc6c2feb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 13:54:58 +0100 Subject: [PATCH 102/131] On Linux, can't even include nmmintrin without explicitly enabling SSE 4.2 support. --- Core/MIPS/IR/IRInterpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index e19be9027a..3c50fecdb7 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -6,7 +6,7 @@ #include "Common/Common.h" #ifdef _M_SSE -#include +#include #endif #if PPSSPP_ARCH(ARM_NEON) From 6ea4924ee91fa1d00613908295fa3d4a50bd6112 Mon Sep 17 00:00:00 2001 From: Kentucky Compass Date: Thu, 4 Jan 2018 09:56:49 -0800 Subject: [PATCH 103/131] require iOS 8, add a launch screen --- CMakeLists.txt | 9 +++++++++ ios/Launch Screen.storyboard | 30 ++++++++++++++++++++++++++++++ ios/PPSSPP-Info.plist | 2 ++ 3 files changed, 41 insertions(+) create mode 100644 ios/Launch Screen.storyboard diff --git a/CMakeLists.txt b/CMakeLists.txt index a73684bf8e..d8cdbee3a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1859,7 +1859,11 @@ if (TargetBin) set_source_files_properties(${SHADER_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION "MacOS/assets/shaders") endif() + if (IOS) + add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource} "ios/Launch Screen.storyboard") + else() add_executable(${TargetBin} MACOSX_BUNDLE ${ICON_PATH_ABS} ${NativeAssets} ${SHADER_FILES} ${FLASH0_FILES} ${LANG_FILES} ${NativeAppSource}) + endif() elseif(WIN32) add_executable(${TargetBin} WIN32 ${NativeAppSource}) set_target_properties(${TargetBin} PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") @@ -1877,6 +1881,7 @@ file(INSTALL assets/flash0 DESTINATION assets) endif() # packaging and code signing if(IOS) + set(DEPLOYMENT_TARGET 8.0) file(GLOB IOSAssets ios/assets/*.png) list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@2x.png) list(REMOVE_ITEM IOSAssets ${CMAKE_CURRENT_SOURCE_DIR}/ios/assets/Default-568h@3x.png) @@ -1887,6 +1892,7 @@ if(IOS) file(INSTALL pspautotests DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/assets) endif() set(RSRC_XIB_FILES assets/Icon@2x.png) + set(RSRC_XIB_FILES "Launch Screen.storyboard") set_source_files_properties(${RSRC_XIB_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources ) @@ -1899,8 +1905,11 @@ if(IOS) COMMAND mkdir -p \"${APP_DIR_NAME}\" COMMAND tar -c -C ${CMAKE_CURRENT_BINARY_DIR} --exclude .DS_Store --exclude .git assets *.png | tar -x -C \"${APP_DIR_NAME}\" ) + set(MACOSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET}) set_target_properties(${TargetBin} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/ios/PPSSPP-Info.plist" + RESOURCE "ios/Launch Screen.storyboard" + XCODE_ATTRIBUTE_IPHONEOS_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET} XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "iPhone/iPad" XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES XCODE_ATTRIBUTE_ENABLE_BITCODE NO diff --git a/ios/Launch Screen.storyboard b/ios/Launch Screen.storyboard new file mode 100644 index 0000000000..5b0377f076 --- /dev/null +++ b/ios/Launch Screen.storyboard @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/PPSSPP-Info.plist b/ios/PPSSPP-Info.plist index 6ef7598045..5893f242c8 100644 --- a/ios/PPSSPP-Info.plist +++ b/ios/PPSSPP-Info.plist @@ -58,5 +58,7 @@ 1 2 + UILaunchStoryboardName + Launch Screen From 18be23ecccc8410377790b02de9a1d625758a6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 19:38:36 +0100 Subject: [PATCH 104/131] IR: More fixes. Still something wrong with VFPU compares (not caused by this PR). --- Core/MIPS/IR/IRCompVFPU.cpp | 2 +- Core/MIPS/IR/IRInterpreter.cpp | 45 ++++++++++++++-------------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index af89d6cb47..82ecffc1da 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -1642,7 +1642,7 @@ namespace MIPSComp { GetVectorRegsPrefixS(sregs, sz, _VS); GetVectorRegsPrefixT(tregs, sz, _VT); - VCondition cond = (VCondition)(op & 0xF); + int cond = op & 0xF; int mask = 0; for (int i = 0; i < n; i++) { ir.Write(IROp::FCmpVfpuBit, cond | (i << 4), sregs[i], tregs[i]); diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 3c50fecdb7..f9dc4a2785 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -321,8 +321,8 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { { #if defined(_M_SSE) __m128i src = _mm_cvtsi32_si128(mips->fi[inst->src1]); + src = _mm_unpacklo_epi8(src, _mm_setzero_si128()); src = _mm_unpacklo_epi16(src, _mm_setzero_si128()); - src = _mm_unpacklo_epi32(src, _mm_setzero_si128()); _mm_store_si128((__m128i *)&mips->fi[inst->dest], _mm_slli_epi32(src, 24)); #else mips->fi[inst->dest] = (mips->fi[inst->src1] << 24); @@ -350,36 +350,25 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Vec4Pack32To8: { -#if defined(_M_SSE) - // Packs the upper bits, so we need to shift down. Then we can just use SSE packing. - __m128i val = _mm_srli_epi32(_mm_load_si128((const __m128i *)&mips->fi[inst->src1]), 24); - val = _mm_packs_epi16(_mm_packs_epi32(val, _mm_setzero_si128()), _mm_setzero_si128()); - mips->fi[inst->dest] = _mm_cvtsi128_si32(val); -#else + // Removed previous SSE code due to the need for unsigned 16-bit pack, which I'm too lazy to work around the lack of in SSE2. + // pshufb or SSE4 instructions can be used instead. u32 val = mips->fi[inst->src1] >> 24; val |= (mips->fi[inst->src1 + 1] >> 16) & 0xFF00; val |= (mips->fi[inst->src1 + 2] >> 8) & 0xFF0000; val |= (mips->fi[inst->src1 + 3]) & 0xFF000000; mips->fi[inst->dest] = val; break; -#endif } case IROp::Vec4Pack31To8: { -#if defined(_M_SSE) - // Packs the upper bits (offset by 1), so we need to shift down and mask. Then we can just use SSE packing. - __m128i val = _mm_srli_epi32(_mm_load_si128((const __m128i *)&mips->fi[inst->src1]), 23); - val = _mm_and_si128(val, _mm_load_si128((const __m128i *)&lowBytesMask)); - val = _mm_packs_epi16(_mm_packs_epi32(val, _mm_setzero_si128()), _mm_setzero_si128()); - mips->fi[inst->dest] = _mm_cvtsi128_si32(val); -#else + // Removed previous SSE code due to the need for unsigned 16-bit pack, which I'm too lazy to work around the lack of in SSE2. + // pshufb or SSE4 instructions can be used instead. u32 val = (mips->fi[inst->src1] >> 23) & 0xFF; val |= (mips->fi[inst->src1 + 1] >> 15) & 0xFF00; val |= (mips->fi[inst->src1 + 2] >> 7) & 0xFF0000; val |= (mips->fi[inst->src1 + 3] << 1) & 0xFF000000; mips->fi[inst->dest] = val; -#endif break; } @@ -394,18 +383,19 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Vec4ClampToZero: { -#if 0 && defined(_M_SSE) - // This is SSE4 only unfortunately, so only suitable for JIT, hence disabled above. +#if defined(_M_SSE) + // Trickery: Expand the sign bit, and use andnot to zero negative values. __m128i val = _mm_load_si128((const __m128i *)&mips->fi[inst->src1]); - val = _mm_max_epi32(val, _mm_setzero_si128()); - mips->fi[inst->dest] = _mm_cvtsi128_si32(val); + __m128i mask = _mm_srai_epi32(val, 31); + val = _mm_andnot_si128(mask, val); + _mm_store_si128((__m128i *)&mips->fi[inst->dest], val); #else for (int i = 0; i < 4; i++) { u32 val = mips->fi[inst->src1 + i]; mips->fi[inst->dest + i] = (int)val >= 0 ? val : 0; } - break; #endif + break; } case IROp::Vec4DuplicateUpperBitsAndShift1: // For vuc2i, the weird one. @@ -450,18 +440,18 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { } else { mips->vfpuCtrl[VFPU_CTRL_CC] &= ~(1 << bit); } - } break; + } case IROp::FCmpVfpuAggregate: { u32 mask = inst->dest; u32 cc = mips->vfpuCtrl[VFPU_CTRL_CC]; - int a = (cc & mask) ? 0x10 : 0x00; - int b = (cc & mask) == mask ? 0x20 : 0x00; - mips->vfpuCtrl[VFPU_CTRL_CC] = (cc & ~0x30) | a | b; - } + int anyBit = (cc & mask) ? 0x10 : 0x00; + int allBit = (cc & mask) == mask ? 0x20 : 0x00; + mips->vfpuCtrl[VFPU_CTRL_CC] = (cc & ~0x30) | anyBit | allBit; break; + } case IROp::FCmovVfpuCC: if (((mips->vfpuCtrl[VFPU_CTRL_CC] >> (inst->src2 & 0xf)) & 1) == ((u32)inst->src2 >> 7)) { @@ -792,6 +782,9 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IRFpCompareMode::False: mips->fpcond = 0; break; + case IRFpCompareMode::NotEqualUnordered: + mips->fpcond = mips->f[inst->src1] != mips->f[inst->src2]; + break; case IRFpCompareMode::EqualOrdered: case IRFpCompareMode::EqualUnordered: mips->fpcond = mips->f[inst->src1] == mips->f[inst->src2]; From 331a8f91e8b31d43dbf59dcefd705752f991cfe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 4 Jan 2018 20:06:26 +0100 Subject: [PATCH 105/131] Fix that weird unordered compare mode, hopefully --- Core/MIPS/IR/IRCompFPU.cpp | 2 +- Core/MIPS/IR/IRInst.h | 2 +- Core/MIPS/IR/IRInterpreter.cpp | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/IR/IRCompFPU.cpp b/Core/MIPS/IR/IRCompFPU.cpp index 55fb033804..ce01c7ff75 100644 --- a/Core/MIPS/IR/IRCompFPU.cpp +++ b/Core/MIPS/IR/IRCompFPU.cpp @@ -111,7 +111,7 @@ void IRFrontend::Comp_FPUComp(MIPSOpcode op) { IRFpCompareMode mode; switch (opc) { case 1: // un, ngle (unordered) - mode = IRFpCompareMode::NotEqualUnordered; + mode = IRFpCompareMode::EitherUnordered; break; case 2: // eq, seq (equal, ordered) mode = IRFpCompareMode::EqualOrdered; diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index 30257c43e7..ba1712b954 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -263,7 +263,7 @@ inline IROp ComparisonToExit(IRComparison comp) { enum IRFpCompareMode { False = 0, - NotEqualUnordered, + EitherUnordered, EqualOrdered, // eq, seq (equal, ordered) EqualUnordered, // ueq, ngl (equal, unordered) LessOrdered, // olt, lt (less than, ordered) diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index f9dc4a2785..e86f88ab7a 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -782,9 +782,13 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IRFpCompareMode::False: mips->fpcond = 0; break; - case IRFpCompareMode::NotEqualUnordered: - mips->fpcond = mips->f[inst->src1] != mips->f[inst->src2]; + case IRFpCompareMode::EitherUnordered: + { + float a = mips->f[inst->src1]; + float b = mips->f[inst->src2]; + mips->fpcond = !(a > b || a < b || a == b); break; + } case IRFpCompareMode::EqualOrdered: case IRFpCompareMode::EqualUnordered: mips->fpcond = mips->f[inst->src1] == mips->f[inst->src2]; From d1d01bfdcbb73d2cdc3fa3018c011cdbb48c6c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 5 Jan 2018 01:20:10 +0100 Subject: [PATCH 106/131] Cleanup GetBlockNumberFromStartAddress --- Core/MIPS/IR/IRJit.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 868c4b6533..3c6b6a511d 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -240,19 +240,18 @@ void IRBlockCache::ComputeStats(BlockCacheStats &bcStats) const { } int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const { - u32 startPage = AddressToPage(em_address); - u32 endPage = AddressToPage(em_address + 4); + u32 page = AddressToPage(em_address); - for (u32 page = startPage; page <= endPage; ++page) { - const auto iter = byPage_.find(page); - if (iter == byPage_.end()) - continue; + const auto iter = byPage_.find(page); + if (iter == byPage_.end()) + return -1; - const std::vector &blocksInPage = iter->second; - for (int i : blocksInPage) { - if (blocks_[i].OverlapsRange(em_address, 4)) { - return i; - } + const std::vector &blocksInPage = iter->second; + for (int i : blocksInPage) { + uint32_t start, size; + blocks_[i].GetRange(start, size); + if (start == em_address) { + return i; } } return -1; From 0bfab27b4601c633884e096fff06add64cf4bdf3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 4 Jan 2018 23:09:03 -0800 Subject: [PATCH 107/131] irjit: Calculate bloat statistics. At least based on IR for now. Can do something else later. memset() was causing a crash on the std::map. --- Core/MIPS/IR/IRJit.cpp | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 3c6b6a511d..87cd45a46a 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -234,9 +234,33 @@ JitBlockDebugInfo IRBlockCache::GetBlockDebugInfo(int blockNum) const { } void IRBlockCache::ComputeStats(BlockCacheStats &bcStats) const { - // TODO: Implement properly - memset(&bcStats, 0, sizeof(bcStats)); + double totalBloat = 0.0; + double maxBloat = 0.0; + double minBloat = 1000000000.0; + for (const auto &b : blocks_) { + double codeSize = (double)b.GetNumInstructions() * sizeof(IRInst); + if (codeSize == 0) + continue; + + u32 origAddr, mipsBytes; + b.GetRange(origAddr, mipsBytes); + double origSize = (double)mipsBytes; + double bloat = codeSize / origSize; + if (bloat < minBloat) { + minBloat = bloat; + bcStats.minBloatBlock = origAddr; + } + if (bloat > maxBloat) { + maxBloat = bloat; + bcStats.maxBloatBlock = origAddr; + } + totalBloat += bloat; + bcStats.bloatMap[bloat] = origAddr; + } bcStats.numBlocks = (int)blocks_.size(); + bcStats.minBloat = minBloat; + bcStats.maxBloat = maxBloat; + bcStats.avgBloat = totalBloat / (double)blocks_.size(); } int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocksOnly) const { From 5b9b6daea850e2af59d771cbd6f0ef83c14b4ffe Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 4 Jan 2018 23:10:49 -0800 Subject: [PATCH 108/131] irjit: Enable more debug UI for block selection. --- UI/DevScreens.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 542a65a225..264ca0c562 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -748,7 +748,7 @@ UI::EventReturn JitCompareScreen::OnAddressChange(UI::EventParams &e) { if (!MIPSComp::jit) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return UI::EVENT_DONE; u32 addr; @@ -815,7 +815,7 @@ UI::EventReturn JitCompareScreen::OnBlockAddress(UI::EventParams &e) { return UI::EVENT_DONE; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return UI::EVENT_DONE; @@ -859,7 +859,7 @@ void JitCompareScreen::OnRandomBlock(int flag) { if (!MIPSComp::jit) { return; } - JitBlockCache *blockCache = MIPSComp::jit->GetBlockCache(); + JitBlockCacheDebugInterface *blockCache = MIPSComp::jit->GetBlockCacheDebugInterface(); if (!blockCache) return; @@ -867,21 +867,25 @@ void JitCompareScreen::OnRandomBlock(int flag) { if (numBlocks > 0) { bool anyWanted = false; int tries = 0; - while (!anyWanted && tries < 10000) { + while (!anyWanted && tries < numBlocks) { currentBlock_ = rand() % numBlocks; - const JitBlock *b = blockCache->GetBlock(currentBlock_); - for (u32 addr = b->originalAddress; addr <= b->originalAddress + b->originalSize; addr += 4) { + JitBlockDebugInfo b = blockCache->GetBlockDebugInfo(currentBlock_); + u32 mipsBytes = (u32)b.origDisasm.size() * 4; + for (u32 addr = b.originalAddress; addr <= b.originalAddress + mipsBytes; addr += 4) { MIPSOpcode opcode = Memory::Read_Instruction(addr); if (MIPSGetInfo(opcode) & flag) { char temp[256]; MIPSDisAsm(opcode, addr, temp); - // INFO_LOG(HLE, "Stopping VFPU instruction: %s", temp); + // INFO_LOG(HLE, "Stopping at random instruction: %08x %s", addr, temp); anyWanted = true; break; } } tries++; } + + if (!anyWanted) + currentBlock_ = -1; } UpdateDisasm(); } From ccd562d934073c0a7086887ea7f14046258ed1e6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 4 Jan 2018 23:24:15 -0800 Subject: [PATCH 109/131] irjit: Add a safety check for block num overflow. In case a game is very frequently modifying some block. --- Core/MIPS/IR/IRJit.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 87cd45a46a..4f11fde897 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -67,6 +67,12 @@ void IRJit::Compile(u32 em_address) { PROFILE_THIS_SCOPE("jitc"); int block_num = blocks_.AllocateBlock(em_address); + if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) { + // Ran out of block numbers - need to reset. + ERROR_LOG(JIT, "Ran out of block numbers, clearing cache"); + ClearCache(); + block_num = blocks_.AllocateBlock(em_address); + } IRBlock *b = blocks_.GetBlock(block_num); std::vector instructions; From 6149ac584f73484bc583795a2ceda393f33b2779 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 6 Jan 2018 16:43:38 -0800 Subject: [PATCH 110/131] jit: Add interface to precompile functions. This doesn't actually do any preloading yet, it just adds an API. --- Core/Config.cpp | 1 + Core/Config.h | 1 + Core/HLE/sceKernelModule.cpp | 6 ++++++ Core/HLE/sceKernelThread.cpp | 3 ++- Core/MIPS/JitCommon/JitCommon.h | 1 + Core/MIPS/MIPSAnalyst.cpp | 25 +++++++++++++++++++++++++ Core/MIPS/MIPSAnalyst.h | 3 ++- 7 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 744695e058..d35c3bc3e9 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -406,6 +406,7 @@ static ConfigSetting cpuSettings[] = { ConfigSetting("FastMemoryAccess", &g_Config.bFastMemory, true, true, true), ReportedConfigSetting("FuncReplacements", &g_Config.bFuncReplacements, true, true, true), ConfigSetting("HideSlowWarnings", &g_Config.bHideSlowWarnings, false, true, false), + ConfigSetting("PreloadFunctions", &g_Config.bPreloadFunctions, false, true, true), ReportedConfigSetting("CPUSpeed", &g_Config.iLockedCPUSpeed, 0, true, true), ConfigSetting(false), diff --git a/Core/Config.h b/Core/Config.h index de342753a8..12534e3e6d 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -126,6 +126,7 @@ public: bool bForceLagSync; bool bFuncReplacements; bool bHideSlowWarnings; + bool bPreloadFunctions; bool bSeparateSASThread; bool bSeparateIOThread; diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 26d54d8794..47a9ae7d84 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -712,6 +712,7 @@ void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting) { } WriteSyscall(func.moduleName, func.nid, func.stubAddr); currentMIPS->InvalidateICache(func.stubAddr, 8); + MIPSAnalyst::PrecompileFunction(func.stubAddr, 8); return; } @@ -730,6 +731,7 @@ void ImportFuncSymbol(const FuncSymbolImport &func, bool reimporting) { } WriteFuncStub(func.stubAddr, it->symAddr); currentMIPS->InvalidateICache(func.stubAddr, 8); + MIPSAnalyst::PrecompileFunction(func.stubAddr, 8); return; } } @@ -768,6 +770,7 @@ void ExportFuncSymbol(const FuncSymbolExport &func) { INFO_LOG(LOADER, "Resolving function %s/%08x", func.moduleName, func.nid); WriteFuncStub(it->stubAddr, func.symAddr); currentMIPS->InvalidateICache(it->stubAddr, 8); + MIPSAnalyst::PrecompileFunction(it->stubAddr, 8); } } } @@ -1450,6 +1453,9 @@ static Module *__KernelLoadELFFromPtr(const u8 *ptr, size_t elfSize, u32 loadAdd // use module_start_func instead of entry_addr if entry_addr is 0 if (module->nm.entry_addr == 0) module->nm.entry_addr = module->nm.module_start_func; + + MIPSAnalyst::PrecompileFunctions(); + } else { module->nm.entry_addr = -1; } diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 0e15bfe1e6..16f71881d0 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -26,7 +26,7 @@ #include "Common/CommonTypes.h" #include "Core/HLE/HLE.h" #include "Core/HLE/HLETables.h" -#include "Core/MIPS/MIPSInt.h" +#include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Core/MIPS/MIPS.h" #include "Core/CoreTiming.h" @@ -887,6 +887,7 @@ static void __KernelWriteFakeSysCall(u32 nid, u32 *ptr, u32 &pos) *ptr = pos; pos += 8; WriteSyscall("FakeSysCalls", nid, *ptr); + MIPSAnalyst::PrecompileFunction(*ptr, 8); } void __KernelThreadingInit() diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index 74341169b5..33e07b1955 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -129,6 +129,7 @@ namespace MIPSComp { virtual void DoState(PointerWrap &p) = 0; virtual void RunLoopUntil(u64 globalticks) = 0; virtual void Compile(u32 em_address) = 0; + virtual void CompileFunction(u32 start_address, u32 length) { } virtual void ClearCache() = 0; virtual MIPSOpcode GetOriginalOp(MIPSOpcode op) = 0; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 99afd042d3..036af24154 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -21,6 +21,7 @@ #include #include +#include "base/timeutil.h" #include "ext/cityhash/city.h" #include "Common/FileUtil.h" #include "Core/Config.h" @@ -904,6 +905,30 @@ skip: } } + void PrecompileFunction(u32 startAddr, u32 length) { + // Direct calls to this ignore the bPreloadFunctions flag, since it's just for stubs. + if (MIPSComp::jit) { + MIPSComp::jit->CompileFunction(startAddr, length); + } + } + + void PrecompileFunctions() { + if (!g_Config.bPreloadFunctions) { + return; + } + std::lock_guard guard(functions_lock); + + double st = real_time_now(); + for (auto iter = functions.begin(), end = functions.end(); iter != end; iter++) { + const AnalyzedFunction &f = *iter; + + PrecompileFunction(f.start, f.end - f.start + 4); + } + double et = real_time_now(); + + NOTICE_LOG(JIT, "Precompiled %d MIPS functions in %0.2f milliseconds", (int)functions.size(), (et - st) * 1000.0); + } + static const char *DefaultFunctionName(char buffer[256], u32 startAddr) { sprintf(buffer, "z_un_%08x", startAddr); return buffer; diff --git a/Core/MIPS/MIPSAnalyst.h b/Core/MIPS/MIPSAnalyst.h index 867d96895c..9d6683c48a 100644 --- a/Core/MIPS/MIPSAnalyst.h +++ b/Core/MIPS/MIPSAnalyst.h @@ -107,7 +107,8 @@ namespace MIPSAnalyst void RegisterFunction(u32 startAddr, u32 size, const char *name); void ScanForFunctions(u32 startAddr, u32 endAddr, bool insertSymbols); void ForgetFunctions(u32 startAddr, u32 endAddr); - void CompileLeafs(); + void PrecompileFunctions(); + void PrecompileFunction(u32 startAddr, u32 length); void SetHashMapFilename(const std::string& filename = ""); void LoadBuiltinHashMap(); From 463b2a90c78eea4a032729299f197d0ade3fcac4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 6 Jan 2018 17:06:53 -0800 Subject: [PATCH 111/131] irjit: Allow precompiling funcs at start. This can take a second, but cuts down on jitc spikes throughout runtime. Note: bits of the game will still be recompiled as games change code. This is basically the same operation as loading from cache, without the cache yet. --- Core/MIPS/IR/IRJit.cpp | 186 ++++++++++++++++++++++++++++++++++---- Core/MIPS/IR/IRJit.h | 20 +++- Core/MIPS/MIPSAnalyst.cpp | 2 + 3 files changed, 187 insertions(+), 21 deletions(-) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 4f11fde897..511a5fe755 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "base/logging.h" +#include "ext/xxhash.h" #include "profiler/profiler.h" #include "Common/ChunkFile.h" #include "Common/StringUtils.h" @@ -66,27 +67,131 @@ void IRJit::InvalidateCacheAt(u32 em_address, int length) { void IRJit::Compile(u32 em_address) { PROFILE_THIS_SCOPE("jitc"); - int block_num = blocks_.AllocateBlock(em_address); - if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) { - // Ran out of block numbers - need to reset. - ERROR_LOG(JIT, "Ran out of block numbers, clearing cache"); - ClearCache(); - block_num = blocks_.AllocateBlock(em_address); + if (g_Config.bPreloadFunctions) { + // Look to see if we've preloaded this block. + int block_num = blocks_.FindPreloadBlock(em_address); + if (block_num != -1) { + IRBlock *b = blocks_.GetBlock(block_num); + // Okay, let's link and finalize the block now. + b->Finalize(block_num); + if (b->IsValid()) { + // Success, we're done. + return; + } + } } - IRBlock *b = blocks_.GetBlock(block_num); std::vector instructions; u32 mipsBytes; - frontend_.DoJit(em_address, instructions, mipsBytes); - b->SetInstructions(instructions); - b->SetOriginalSize(mipsBytes); - // Overwrites the first instruction, and also updates stats. - blocks_.FinalizeBlock(block_num); + if (!CompileBlock(em_address, instructions, mipsBytes, false)) { + // Ran out of block numbers - need to reset. + ERROR_LOG(JIT, "Ran out of block numbers, clearing cache"); + ClearCache(); + CompileBlock(em_address, instructions, mipsBytes, false); + } if (frontend_.CheckRounding(em_address)) { // Our assumptions are all wrong so it's clean-slate time. ClearCache(); - Compile(em_address); + CompileBlock(em_address, instructions, mipsBytes, false); + } +} + +bool IRJit::CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { + int block_num = blocks_.AllocateBlock(em_address); + if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) { + // Out of block numbers. Caller will handle. + return false; + } + + frontend_.DoJit(em_address, instructions, mipsBytes); + + IRBlock *b = blocks_.GetBlock(block_num); + b->SetInstructions(instructions); + b->SetOriginalSize(mipsBytes); + if (preload) { + // Hash, then only update page stats, don't link yet. + b->UpdateHash(); + blocks_.FinalizeBlock(block_num, true); + } else { + // Overwrites the first instruction, and also updates stats. + // TODO: Should we always hash? Then we can reuse blocks. + blocks_.FinalizeBlock(block_num); + } + + return true; +} + +void IRJit::CompileFunction(u32 start_address, u32 length) { + PROFILE_THIS_SCOPE("jitc"); + + // Note: we don't actually write emuhacks yet, so we can validate hashes. + // This way, if the game changes the code afterward, we'll catch even without icache invalidation. + + // We may go up and down from branches, so track all block starts done here. + std::set doneAddresses; + std::vector pendingAddresses; + pendingAddresses.push_back(start_address); + while (!pendingAddresses.empty()) { + u32 em_address = pendingAddresses.back(); + pendingAddresses.pop_back(); + + // To be safe, also check if a real block is there. This can be a runtime module load. + u32 inst = Memory::ReadUnchecked_U32(em_address); + if (MIPS_IS_RUNBLOCK(inst) || doneAddresses.find(em_address) != doneAddresses.end()) { + // Already compiled this address. + continue; + } + + std::vector instructions; + u32 mipsBytes; + if (!CompileBlock(em_address, instructions, mipsBytes, true)) { + // Ran out of block numbers - let's hope there's no more code it needs to run. + // Will flush when actually compiling. + ERROR_LOG(JIT, "Ran out of block numbers while compiling function"); + return; + } + + doneAddresses.insert(em_address); + + for (const IRInst &inst : instructions) { + u32 exit = 0; + + switch (inst.op) { + case IROp::ExitToConst: + case IROp::ExitToConstIfEq: + case IROp::ExitToConstIfNeq: + case IROp::ExitToConstIfGtZ: + case IROp::ExitToConstIfGeZ: + case IROp::ExitToConstIfLtZ: + case IROp::ExitToConstIfLeZ: + case IROp::ExitToConstIfFpTrue: + case IROp::ExitToConstIfFpFalse: + exit = inst.constant; + break; + + case IROp::ExitToPC: + case IROp::Break: + // Don't add any, we'll do block end anyway (for jal, etc.) + exit = 0; + break; + + default: + exit = 0; + break; + } + + // Only follow jumps internal to the function. + if (exit != 0 && exit >= start_address && exit < start_address + length) { + // Even if it's a duplicate, we check at loop start. + pendingAddresses.push_back(exit); + } + } + + // Also include after the block for jal returns. + if (em_address + mipsBytes < start_address + length) { + pendingAddresses.push_back(em_address + mipsBytes); + } } } @@ -166,8 +271,10 @@ void IRBlockCache::InvalidateICache(u32 address, u32 length) { } } -void IRBlockCache::FinalizeBlock(int i) { - blocks_[i].Finalize(i); +void IRBlockCache::FinalizeBlock(int i, bool preload) { + if (!preload) { + blocks_[i].Finalize(i); + } u32 startAddr, size; blocks_[i].GetRange(startAddr, size); @@ -185,6 +292,27 @@ u32 IRBlockCache::AddressToPage(u32 addr) const { return (addr & 0x3FFFFFFF) >> 10; } +int IRBlockCache::FindPreloadBlock(u32 em_address) { + u32 page = AddressToPage(em_address); + auto iter = byPage_.find(page); + if (iter == byPage_.end()) + return -1; + + const std::vector &blocksInPage = iter->second; + for (int i : blocksInPage) { + u32 start, mipsBytes; + blocks_[i].GetRange(start, mipsBytes); + + if (start == em_address) { + if (blocks_[i].HashMatches()) { + return i; + } + } + } + + return -1; +} + std::vector IRBlockCache::SaveAndClearEmuHackOps() { std::vector result; result.resize(blocks_.size()); @@ -301,9 +429,13 @@ bool IRBlock::RestoreOriginalFirstOp(int number) { } void IRBlock::Finalize(int number) { - origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_); - MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number); - Memory::Write_Opcode_JIT(origAddr_, opcode); + // Check it wasn't invalidated, in case this is after preload. + // TODO: Allow reusing blocks when the code matches hash_ again, instead. + if (origAddr_) { + origFirstOpcode_ = Memory::Read_Opcode_JIT(origAddr_); + MIPSOpcode opcode = MIPSOpcode(MIPS_EMUHACK_OPCODE | number); + Memory::Write_Opcode_JIT(origAddr_, opcode); + } } void IRBlock::Destroy(int number) { @@ -317,6 +449,24 @@ void IRBlock::Destroy(int number) { } } +u64 IRBlock::CalculateHash() const { + if (origAddr_) { + // This is unfortunate. In case of emuhacks, we have to make a copy. + std::vector buffer; + buffer.resize(origSize_ / 4); + size_t pos = 0; + for (u32 off = 0; off < origSize_; off += 4) { + // Let's actually hash the replacement, if any. + MIPSOpcode instr = Memory::ReadUnchecked_Instruction(origAddr_ + off, false); + buffer[pos++] = instr.encoding; + } + + return XXH64(&buffer[0], origSize_, 0x9A5C33B8); + } + + return 0; +} + bool IRBlock::OverlapsRange(u32 addr, u32 size) const { addr &= 0x3FFFFFFF; u32 origAddr = origAddr_ & 0x3FFFFFFF; diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index 7e3055c253..beefcf9bcb 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -46,6 +46,7 @@ public: origAddr_ = b.origAddr_; origSize_ = b.origSize_; origFirstOpcode_ = b.origFirstOpcode_; + hash_ = b.hash_; b.instr_ = nullptr; } @@ -66,10 +67,16 @@ public: MIPSOpcode GetOriginalFirstOp() const { return origFirstOpcode_; } bool HasOriginalFirstOp() const; bool RestoreOriginalFirstOp(int number); - bool IsValid() const { return origAddr_ != 0; } + bool IsValid() const { return origAddr_ != 0 && origFirstOpcode_.encoding != 0x68FFFFFF; } void SetOriginalSize(u32 size) { origSize_ = size; } + void UpdateHash() { + hash_ = CalculateHash(); + } + bool HashMatches() const { + return origAddr_ && hash_ == CalculateHash(); + } bool OverlapsRange(u32 addr, u32 size) const; void GetRange(u32 &start, u32 &size) const { @@ -81,11 +88,14 @@ public: void Destroy(int number); private: + u64 CalculateHash() const; + IRInst *instr_; u16 numInstructions_; u32 origAddr_; u32 origSize_; - MIPSOpcode origFirstOpcode_; + u64 hash_ = 0; + MIPSOpcode origFirstOpcode_ = MIPSOpcode(0x68FFFFFF); }; class IRBlockCache : public JitBlockCacheDebugInterface { @@ -93,7 +103,7 @@ public: IRBlockCache() {} void Clear(); void InvalidateICache(u32 address, u32 length); - void FinalizeBlock(int i); + void FinalizeBlock(int i, bool preload = false); int GetNumBlocks() const override { return (int)blocks_.size(); } int AllocateBlock(int emAddr) { blocks_.push_back(IRBlock(emAddr)); @@ -107,6 +117,8 @@ public: } } + int FindPreloadBlock(u32 em_address); + std::vector SaveAndClearEmuHackOps(); void RestoreSavedEmuHackOps(std::vector saved); @@ -133,6 +145,7 @@ public: void RunLoopUntil(u64 globalticks) override; void Compile(u32 em_address) override; // Compiles a block at current MIPS PC + void CompileFunction(u32 start_address, u32 length) override; bool DescribeCodePtr(const u8 *ptr, std::string &name) override; // Not using a regular block cache. @@ -152,6 +165,7 @@ public: void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; private: + bool CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); bool ReplaceJalTo(u32 dest); JitOptions jo; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 036af24154..98d07490ab 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -918,6 +918,8 @@ skip: } std::lock_guard guard(functions_lock); + // TODO: Load from cache file if available instead. + double st = real_time_now(); for (auto iter = functions.begin(), end = functions.end(); iter != end; iter++) { const AnalyzedFunction &f = *iter; From cc8e9a93c32b4d49f304f75aeec27f355df49681 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 6 Jan 2018 17:08:54 -0800 Subject: [PATCH 112/131] irjit: For debug, return the best block at addr. Invalidation may result in multiple matching blocks, prefer any that is currently valid. --- Core/MIPS/IR/IRJit.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 511a5fe755..9c8fefeebf 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -405,14 +405,18 @@ int IRBlockCache::GetBlockNumberFromStartAddress(u32 em_address, bool realBlocks return -1; const std::vector &blocksInPage = iter->second; + int best = -1; for (int i : blocksInPage) { uint32_t start, size; blocks_[i].GetRange(start, size); if (start == em_address) { - return i; + best = i; + if (blocks_[i].IsValid()) { + return i; + } } } - return -1; + return best; } bool IRBlock::HasOriginalFirstOp() const { From 97674b80bddc07f26308803892e28818a19bfba7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 6 Jan 2018 17:23:53 -0800 Subject: [PATCH 113/131] irjit: Skip preloading blocks with jump to 0. These will be changed before executing anyway. --- Core/MIPS/IR/IRCompBranch.cpp | 9 +++++---- Core/MIPS/IR/IRFrontend.cpp | 8 +++++++- Core/MIPS/IR/IRFrontend.h | 2 +- Core/MIPS/IR/IRJit.cpp | 9 +++++++-- Core/MIPS/JitCommon/JitState.h | 24 ++++++++---------------- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index bb08883cc1..20d352b5c1 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -295,11 +295,12 @@ void IRFrontend::Comp_Jump(MIPSOpcode op) { // Might be a stubbed address or something? if (!Memory::IsValidAddress(targetAddr)) { - if (js.nextExit == 0) { + // If preloading, flush - this block will likely be fixed later. + if (js.preloading) + js.cancel = true; + else ERROR_LOG_REPORT(JIT, "Jump to invalid address: %08x", targetAddr); - } else { - js.compiling = false; - } + js.compiling = false; // TODO: Mark this block dirty or something? May be indication it will be changed by imports. return; } diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 8174fa17f2..ad1fc94e0e 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -219,8 +219,9 @@ MIPSOpcode IRFrontend::GetOffsetInstruction(int offset) { return Memory::Read_Instruction(GetCompilerPC() + 4 * offset); } -void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes) { +void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { js.cancel = false; + js.preloading = preload; js.blockStart = em_address; js.compilerPC = em_address; js.lastContinuedPC = 0; @@ -246,6 +247,11 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &m js.numInstructions++; } + if (js.cancel) { + // Clear the instructions to signal this was not compiled. + ir.Clear(); + } + mipsBytes = js.compilerPC - em_address; IRWriter simplified; diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index 70194cef1b..73b63924f3 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -88,7 +88,7 @@ public: void DoState(PointerWrap &p); bool CheckRounding(u32 blockAddress); // returns true if we need a do-over - void DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes); + void DoJit(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload); void EatPrefix() override { js.EatPrefix(); diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 9c8fefeebf..9a86a4356f 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -98,14 +98,19 @@ void IRJit::Compile(u32 em_address) { } bool IRJit::CompileBlock(u32 em_address, std::vector &instructions, u32 &mipsBytes, bool preload) { + frontend_.DoJit(em_address, instructions, mipsBytes, preload); + if (instructions.empty()) { + _dbg_assert_(JIT, preload); + // We return true when preloading so it doesn't abort. + return preload; + } + int block_num = blocks_.AllocateBlock(em_address); if ((block_num & ~MIPS_EMUHACK_VALUE_MASK) != 0) { // Out of block numbers. Caller will handle. return false; } - frontend_.DoJit(em_address, instructions, mipsBytes); - IRBlock *b = blocks_.GetBlock(block_num); b->SetInstructions(instructions); b->SetOriginalSize(mipsBytes); diff --git a/Core/MIPS/JitCommon/JitState.h b/Core/MIPS/JitCommon/JitState.h index 4e1d2c8898..6d2c8d0937 100644 --- a/Core/MIPS/JitCommon/JitState.h +++ b/Core/MIPS/JitCommon/JitState.h @@ -56,15 +56,6 @@ namespace MIPSComp { AFTER_MEMCHECK_CLEANUP = 0x04, }; - JitState() - : hasSetRounding(0), - lastSetRounding(0), - currentRoundingFunc(nullptr), - startDefaultPrefix(true), - prefixSFlag(PREFIX_UNKNOWN), - prefixTFlag(PREFIX_UNKNOWN), - prefixDFlag(PREFIX_UNKNOWN) {} - u32 compilerPC; u32 blockStart; u32 lastContinuedPC; @@ -78,20 +69,21 @@ namespace MIPSComp { int numInstructions; bool compiling; // TODO: get rid of this in favor of using analysis results to determine end of block bool hadBreakpoints; + bool preloading = false; JitBlock *curBlock; - u8 hasSetRounding; - u8 lastSetRounding; - const u8 *currentRoundingFunc; + u8 hasSetRounding = 0; + u8 lastSetRounding = 0; + const u8 *currentRoundingFunc = nullptr; // VFPU prefix magic - bool startDefaultPrefix; + bool startDefaultPrefix = true; u32 prefixS; u32 prefixT; u32 prefixD; - PrefixState prefixSFlag; - PrefixState prefixTFlag; - PrefixState prefixDFlag; + PrefixState prefixSFlag = PREFIX_UNKNOWN; + PrefixState prefixTFlag = PREFIX_UNKNOWN; + PrefixState prefixDFlag = PREFIX_UNKNOWN; void PrefixStart() { if (startDefaultPrefix) { From cd3f4881a5bc37a566cb68ad11837fa88c37b1cd Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 12:11:16 -0800 Subject: [PATCH 114/131] irjit: Optimize out temp lhs copies. Common example: li v0, 1 beq s2, v0, somewhere li v0, 2 Which was copying s2 before. This pattern generally doesn't happen in MIPS code, though, so really only catches that (very common) case. --- Core/MIPS/IR/IRPassSimplify.cpp | 44 ++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index f3798c1dfb..d15183ce29 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -558,6 +558,22 @@ bool IRReadsFromGPR(const IRInst &inst, int reg) { return false; } +IRInst IRReplaceSrcGPR(const IRInst &inst, int fromReg, int toReg) { + IRInst newInst = inst; + const IRMeta *m = GetIRMeta(inst.op); + + if (m->types[1] == 'G' && inst.src1 == fromReg) { + newInst.src1 = toReg; + } + if (m->types[2] == 'G' && inst.src2 == fromReg) { + newInst.src2 = toReg; + } + if ((m->flags & (IRFLAG_SRC3 | IRFLAG_SRC3DST)) != 0 && m->types[0] == 'G' && inst.src3 == fromReg) { + newInst.src3 = toReg; + } + return newInst; +} + int IRDestGPR(const IRInst &inst) { const IRMeta *m = GetIRMeta(inst.op); @@ -576,14 +592,17 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { } int reg; + int srcReg = -1; int index; bool readByExit; }; std::vector checks; + int lastWrittenTo[256]; + memset(lastWrittenTo, -1, sizeof(lastWrittenTo)); bool logBlocks = false; for (int i = 0, n = (int)in.GetInstructions().size(); i < n; i++) { - const IRInst &inst = in.GetInstructions()[i]; + IRInst inst = in.GetInstructions()[i]; const IRMeta *m = GetIRMeta(inst.op); for (Check &check : checks) { @@ -592,8 +611,16 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { } if (IRReadsFromGPR(inst, check.reg)) { - // Read from, so we can't optimize out. - check.reg = 0; + // Read from, but was this just a copy? + bool mutatesReg = (m->flags & IRFLAG_SRC3DST) != 0 && m->types[0] == 'G' && inst.src3 == check.reg; + bool cannotReplace = inst.op == IROp::Interpret || inst.op == IROp::CallReplacement; + if (!mutatesReg && !cannotReplace && check.srcReg >= 0 && lastWrittenTo[check.srcReg] < check.index) { + // Replace with the srcReg instead. This happens with non-nice delay slots. + inst = IRReplaceSrcGPR(inst, check.reg, check.srcReg); + } else { + // Legitimately read from, so we can't optimize out. + check.reg = 0; + } } else if (check.readByExit && (m->flags & IRFLAG_EXIT) != 0) { check.reg = 0; } else if (IRDestGPR(inst) == check.reg) { @@ -616,10 +643,19 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { case IRTEMP_RHS: // Unlike other ops, these don't need to persist between blocks. // So we consider them not read unless proven read. - checks.push_back(Check(dest, i, false)); + lastWrittenTo[dest] = i; + // If this is a copy, we might be able to optimize out the copy. + if (inst.op == IROp::Mov) { + Check check(dest, i, false); + check.srcReg = inst.src1; + checks.push_back(check); + } else { + checks.push_back(Check(dest, i, false)); + } break; default: + lastWrittenTo[dest] = i; if (dest > IRTEMP_RHS) { // These might sometimes be implicitly read/written by other instructions. break; From 6dda0533651bd732aa92d8c4edeb2f979644a06f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 12:37:07 -0800 Subject: [PATCH 115/131] irjit: Add dedicated ops for lwl/swl and friends. Temporarily removes optimizations. --- Core/MIPS/IR/IRCompLoadStore.cpp | 8 +++++-- Core/MIPS/IR/IRInst.cpp | 8 +++++++ Core/MIPS/IR/IRInst.h | 8 +++++++ Core/MIPS/IR/IRInterpreter.cpp | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index cc17467ffd..9a44307f50 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -198,12 +198,16 @@ namespace MIPSComp { break; case 34: //lwl + ir.Write(IROp::Load32Left, rt, rs, ir.AddConstant(offset)); + break; case 38: //lwr - Comp_ITypeMemLR(op, true); + ir.Write(IROp::Load32Right, rt, rs, ir.AddConstant(offset)); break; case 42: //swl + ir.Write(IROp::Store32Left, rt, rs, ir.AddConstant(offset)); + break; case 46: //swr - Comp_ITypeMemLR(op, false); + ir.Write(IROp::Store32Right, rt, rs, ir.AddConstant(offset)); break; default: diff --git a/Core/MIPS/IR/IRInst.cpp b/Core/MIPS/IR/IRInst.cpp index 2bd21d3fc3..7ad98f849f 100644 --- a/Core/MIPS/IR/IRInst.cpp +++ b/Core/MIPS/IR/IRInst.cpp @@ -71,11 +71,15 @@ static const IRMeta irMeta[] = { { 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::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::StoreFloat, "StoreFloat", "FGC", IRFLAG_SRC3 }, { IROp::StoreVec4, "StoreVec4", "VGC", IRFLAG_SRC3 }, { IROp::FAdd, "FAdd", "FFF" }, @@ -206,6 +210,10 @@ const char *GetGPRName(int r) { 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)"; } } diff --git a/Core/MIPS/IR/IRInst.h b/Core/MIPS/IR/IRInst.h index ba1712b954..cccc1ba55b 100644 --- a/Core/MIPS/IR/IRInst.h +++ b/Core/MIPS/IR/IRInst.h @@ -90,12 +90,16 @@ enum class IROp : u8 { Load16, Load16Ext, Load32, + Load32Left, + Load32Right, LoadFloat, LoadVec4, Store8, Store16, Store32, + Store32Left, + Store32Right, StoreFloat, StoreVec4, @@ -279,6 +283,10 @@ enum { IRTEMP_3, IRTEMP_LHS, // Reserved for use in branches IRTEMP_RHS, // Reserved for use in branches + IRTEMP_LR_ADDR, // Reserved for left/right loads and stores. + IRTEMP_LR_VALUE, // Reserved for left/right loads and stores. + IRTEMP_LR_MASK, // Reserved for left/right loads and stores. + IRTEMP_LR_SHIFT, // Reserved for left/right loads and stores. IRVTEMP_PFX_S = 224 - 32, // Relative to the FP regs IRVTEMP_PFX_T = 228 - 32, diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index e86f88ab7a..dee961f98e 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -145,6 +145,24 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Load32: mips->r[inst->dest] = Memory::ReadUnchecked_U32(mips->r[inst->src1] + inst->constant); break; + case IROp::Load32Left: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 destMask = 0x00ffffff >> shift; + mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem << (24 - shift)); + break; + } + case IROp::Load32Right: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 destMask = 0xffffff00 << (24 - shift); + mips->r[inst->dest] = (mips->r[inst->dest] & destMask) | (mem >> shift); + break; + } case IROp::LoadFloat: mips->f[inst->dest] = Memory::ReadUnchecked_Float(mips->r[inst->src1] + inst->constant); break; @@ -158,6 +176,26 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { case IROp::Store32: Memory::WriteUnchecked_U32(mips->r[inst->src3], mips->r[inst->src1] + inst->constant); break; + case IROp::Store32Left: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 memMask = 0xffffff00 << shift; + u32 result = (mips->r[inst->src3] >> (24 - shift)) | (mem & memMask); + Memory::WriteUnchecked_U32(result, addr & 0xfffffffc); + break; + } + case IROp::Store32Right: + { + u32 addr = mips->r[inst->src1] + inst->constant; + u32 shift = (addr & 3) * 8; + u32 mem = Memory::ReadUnchecked_U32(addr & 0xfffffffc); + u32 memMask = 0x00ffffff >> (24 - shift); + u32 result = (mips->r[inst->src3] << shift) | (mem & memMask); + Memory::WriteUnchecked_U32(result, addr & 0xfffffffc); + break; + } case IROp::StoreFloat: Memory::WriteUnchecked_Float(mips->f[inst->src3], mips->r[inst->src1] + inst->constant); break; From c6d690e9b845fbf1fb773ddd25370362de230fae Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 18:21:26 -0800 Subject: [PATCH 116/131] irjit: Handle Left/Right ops in passes. --- Core/MIPS/IR/IRPassSimplify.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index d15183ce29..b055fa3a1c 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -375,6 +375,8 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::Store8: case IROp::Store16: case IROp::Store32: + case IROp::Store32Left: + case IROp::Store32Right: if (gpr.IsImm(inst.src1) && inst.src1 != inst.dest) { gpr.MapIn(inst.dest); out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); @@ -415,6 +417,16 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts goto doDefault; } break; + case IROp::Load32Left: + case IROp::Load32Right: + if (gpr.IsImm(inst.src1)) { + gpr.MapIn(inst.dest); + out.Write(inst.op, inst.dest, 0, out.AddConstant(gpr.GetImm(inst.src1) + inst.constant)); + } else { + gpr.MapInIn(inst.dest, inst.src1); + goto doDefault; + } + break; case IROp::Downcount: case IROp::SetPCConst: @@ -760,6 +772,8 @@ static std::vector ReorderLoadStoreOps(std::vector &ops) { case IROp::Load16: case IROp::Load16Ext: case IROp::Load32: + case IROp::Load32Left: + case IROp::Load32Right: modifiesReg = true; if (ops[i].src1 == ops[i].dest) { // Can't ever reorder these, since it changes. @@ -770,6 +784,8 @@ static std::vector ReorderLoadStoreOps(std::vector &ops) { case IROp::Store8: case IROp::Store16: case IROp::Store32: + case IROp::Store32Left: + case IROp::Store32Right: break; case IROp::LoadFloat: @@ -868,6 +884,8 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) case IROp::Load16: case IROp::Load16Ext: case IROp::Load32: + case IROp::Load32Left: + case IROp::Load32Right: // To move a load up, its dest can't be changed by things we move down. if (otherRegs[inst.dest] != RegState::UNUSED || otherRegs[inst.src1] == RegState::CHANGED) { flushQueue(); @@ -880,6 +898,8 @@ bool ReorderLoadStore(const IRWriter &in, IRWriter &out, const IROptions &opts) case IROp::Store8: case IROp::Store16: case IROp::Store32: + case IROp::Store32Left: + case IROp::Store32Right: // A store can move above even if it's read, as long as it's not changed by the other ops. if (otherRegs[inst.src3] == RegState::CHANGED || otherRegs[inst.src1] == RegState::CHANGED) { flushQueue(); From b11f00cead9dd5a9686f72d47de94185b5c2ebd6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 18:37:06 -0800 Subject: [PATCH 117/131] irjit: Combine lwl/lwr and swl/swr, like before. Still want to inline the operation, because the backend shouldn't have to redo it every time, and we want the temps cleaned up if possible. --- Core/MIPS/IR/IRFrontend.cpp | 1 + Core/MIPS/IR/IRPassSimplify.cpp | 62 +++++++++++++++++++++++++++++++++ Core/MIPS/IR/IRPassSimplify.h | 1 + 3 files changed, 64 insertions(+) diff --git a/Core/MIPS/IR/IRFrontend.cpp b/Core/MIPS/IR/IRFrontend.cpp index 8174fa17f2..388b81277f 100644 --- a/Core/MIPS/IR/IRFrontend.cpp +++ b/Core/MIPS/IR/IRFrontend.cpp @@ -252,6 +252,7 @@ void IRFrontend::DoJit(u32 em_address, std::vector &instructions, u32 &m IRWriter *code = &ir; if (!js.hadBreakpoints) { static const IRPassFunc passes[] = { + &RemoveLoadStoreLeftRight, &OptimizeFPMoves, &PropagateConstants, &PurgeTemps, diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index b055fa3a1c..033d306e45 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -199,6 +199,68 @@ bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts) { return logBlocks; } +bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions &opts) { + IRInst dummy{ IROp::Nop }; + + bool logBlocks = false; + for (int i = 0, n = (int)in.GetInstructions().size(); i < n; ++i) { + const IRInst &inst = in.GetInstructions()[i]; + const IRInst &next = i + 1 < n ? in.GetInstructions()[i + 1] : dummy; + + // TODO: Reorder or look ahead to combine? + auto combineOpposite = [&](IROp matchOp, int matchOff, IROp replaceOp, int replaceOff) { + if (!opts.unalignedLoadStore) + return false; + if (next.op != matchOp || next.dest != inst.dest || next.src1 != inst.src1) + return false; + if (inst.constant + matchOff != next.constant) + return false; + + // Write out one unaligned op. + out.Write(replaceOp, inst.dest, inst.src1, out.AddConstant(inst.constant + replaceOff)); + // Skip the next one, replaced. + i++; + return true; + }; + + switch (inst.op) { + case IROp::Load32Left: + if (!combineOpposite(IROp::Load32Right, -3, IROp::Load32, -3)) { + // TODO: Flatten. + out.Write(inst); + } + break; + + case IROp::Load32Right: + if (!combineOpposite(IROp::Load32Left, 3, IROp::Load32, 0)) { + // TODO: Flatten. + out.Write(inst); + } + break; + + case IROp::Store32Left: + if (!combineOpposite(IROp::Store32Right, -3, IROp::Store32, -3)) { + // TODO: Flatten. + out.Write(inst); + } + break; + + case IROp::Store32Right: + if (!combineOpposite(IROp::Store32Left, 3, IROp::Store32, 0)) { + // TODO: Flatten. + out.Write(inst); + } + break; + + default: + out.Write(inst); + break; + } + } + + return logBlocks; +} + bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts) { IRRegCache gpr(&out); diff --git a/Core/MIPS/IR/IRPassSimplify.h b/Core/MIPS/IR/IRPassSimplify.h index 118b44128f..e3020a4f61 100644 --- a/Core/MIPS/IR/IRPassSimplify.h +++ b/Core/MIPS/IR/IRPassSimplify.h @@ -6,6 +6,7 @@ typedef bool (*IRPassFunc)(const IRWriter &in, IRWriter &out, const IROptions &o bool IRApplyPasses(const IRPassFunc *passes, size_t c, const IRWriter &in, IRWriter &out, const IROptions &opts); // Block optimizer passes of varying usefulness. +bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions &opts); bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts); bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts); bool ReduceLoads(const IRWriter &in, IRWriter &out, const IROptions &opts); From d27e42865977d40d2b90258976b2400e707b1952 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 19:51:35 -0800 Subject: [PATCH 118/131] irjit: Convert lwr and friends to easier code. This makes it easier to write a (working) jit backend from IR, since these ops are always annoying to get right. --- Core/MIPS/IR/IRCompLoadStore.cpp | 109 +------------------------------ Core/MIPS/IR/IRPassSimplify.cpp | 100 +++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 117 deletions(-) diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index 9a44307f50..14dcbe45be 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -46,113 +46,8 @@ namespace MIPSComp { void IRFrontend::Comp_ITypeMemLR(MIPSOpcode op, bool load) { CONDITIONAL_DISABLE; - - int offset = _IMM16; - MIPSGPReg rt = _RT; - MIPSGPReg rs = _RS; - int o = op >> 26; - - if (!js.inDelaySlot && opts.unalignedLoadStore) { - // Optimisation: Combine to single unaligned load/store. - const bool isLeft = (o == 34 || o == 42); - MIPSOpcode nextOp = GetOffsetInstruction(1); - // Find a matching shifted load/store in opposite direction with opposite offset. - if (nextOp == (isLeft ? (op.encoding + (4 << 26) - 3) : (op.encoding - (4 << 26) + 3))) { - EatInstruction(nextOp); - - if (isLeft) { - // Get the unaligned base offset from the lwr/swr instruction. - offset = (signed short)(nextOp & 0xFFFF); - // Already checked it if we're on the lwr. - CheckMemoryBreakpoint(rs, offset); - } - - if (load) { - ir.Write(IROp::Load32, rt, rs, ir.AddConstant(offset)); - } else { - ir.Write(IROp::Store32, rt, rs, ir.AddConstant(offset)); - } - return; - } - } - - int addrReg = IRTEMP_0; - int valueReg = IRTEMP_1; - int maskReg = IRTEMP_2; - int shiftReg = IRTEMP_3; - - // addrReg = rs + imm - ir.Write(IROp::AddConst, addrReg, rs, ir.AddConstant(offset)); - // shiftReg = (addr & 3) * 8 - ir.Write(IROp::AndConst, shiftReg, addrReg, ir.AddConstant(3)); - ir.Write(IROp::ShlImm, shiftReg, shiftReg, 3); - // addrReg = addr & 0xfffffffc (for stores, later) - ir.Write(IROp::AndConst, addrReg, addrReg, ir.AddConstant(0xFFFFFFFC)); - // valueReg = RAM(addrReg) - ir.Write(IROp::Load32, valueReg, addrReg, ir.AddConstant(0)); - - switch (o) { - case 34: //lwl - // rt &= (0x00ffffff >> shift) - // Alternatively, could shift to a wall and back (but would require two shifts each way.) - ir.WriteSetConstant(maskReg, 0x00ffffff); - ir.Write(IROp::Shr, maskReg, maskReg, shiftReg); - ir.Write(IROp::And, rt, rt, maskReg); - // valueReg <<= (24 - shift) - ir.Write(IROp::Neg, shiftReg, shiftReg); - ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); - ir.Write(IROp::Shl, valueReg, valueReg, shiftReg); - // rt |= valueReg - ir.Write(IROp::Or, rt, rt, valueReg); - break; - case 38: //lwr - // valueReg >>= shift - ir.Write(IROp::Shr, valueReg, valueReg, shiftReg); - // shiftReg = 24 - shift - ir.Write(IROp::Neg, shiftReg, shiftReg); - ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); - // rt &= (0xffffff00 << (24 - shift)) - // Alternatively, could shift to a wall and back (but would require two shifts each way.) - ir.WriteSetConstant(maskReg, 0xffffff00); - ir.Write(IROp::Shl, maskReg, maskReg, shiftReg); - ir.Write(IROp::And, rt, rt, maskReg); - // rt |= valueReg - ir.Write(IROp::Or, rt, rt, valueReg); - break; - case 42: //swl - // valueReg &= 0xffffff00 << shift - ir.WriteSetConstant(maskReg, 0xffffff00); - ir.Write(IROp::Shl, maskReg, maskReg, shiftReg); - ir.Write(IROp::And, valueReg, valueReg, maskReg); - // shiftReg = 24 - shift - ir.Write(IROp::Neg, shiftReg, shiftReg); - ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); - // valueReg |= rt >> (24 - shift) - ir.Write(IROp::Shr, maskReg, rt, shiftReg); - ir.Write(IROp::Or, valueReg, valueReg, maskReg); - break; - case 46: //swr - // valueReg &= 0x00ffffff << (24 - shift) - ir.WriteSetConstant(maskReg, 0x00ffffff); - ir.Write(IROp::Neg, shiftReg, shiftReg); - ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); - ir.Write(IROp::Shr, maskReg, maskReg, shiftReg); - ir.Write(IROp::And, valueReg, valueReg, maskReg); - ir.Write(IROp::Neg, shiftReg, shiftReg); - ir.Write(IROp::AddConst, shiftReg, shiftReg, ir.AddConstant(24)); - // valueReg |= rt << shift - ir.Write(IROp::Shl, maskReg, rt, shiftReg); - ir.Write(IROp::Or, valueReg, valueReg, maskReg); - break; - default: - INVALIDOP; - return; - } - - if (!load) { - // RAM(addrReg) = valueReg - ir.Write(IROp::Store32, valueReg, addrReg, ir.AddConstant(0)); - } + // Should never get here, done in Comp_ITypeMem(). + INVALIDOP; } void IRFrontend::Comp_ITypeMem(MIPSOpcode op) { diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index 033d306e45..f365128e44 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -208,6 +208,7 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions const IRInst &next = i + 1 < n ? in.GetInstructions()[i + 1] : dummy; // TODO: Reorder or look ahead to combine? + auto combineOpposite = [&](IROp matchOp, int matchOff, IROp replaceOp, int replaceOff) { if (!opts.unalignedLoadStore) return false; @@ -223,32 +224,90 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions return true; }; + auto addCommonProlog = [&]() { + // IRTEMP_LR_ADDR = rs + imm + out.Write(IROp::AddConst, IRTEMP_LR_ADDR, inst.src1, out.AddConstant(inst.constant)); + // IRTEMP_LR_SHIFT = (addr & 3) * 8 + out.Write(IROp::AndConst, IRTEMP_LR_SHIFT, IRTEMP_LR_ADDR, out.AddConstant(3)); + out.Write(IROp::ShlImm, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, 3); + // IRTEMP_LR_ADDR = addr & 0xfffffffc (for stores, later) + out.Write(IROp::AndConst, IRTEMP_LR_ADDR, IRTEMP_LR_ADDR, out.AddConstant(0xFFFFFFFC)); + // IRTEMP_LR_VALUE = RAM(IRTEMP_LR_ADDR) + out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(0)); + }; + auto addCommonStore = [&](int off = 0) { + // RAM(IRTEMP_LR_ADDR) = IRTEMP_LR_VALUE + out.Write(IROp::Store32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(off)); + }; + switch (inst.op) { case IROp::Load32Left: if (!combineOpposite(IROp::Load32Right, -3, IROp::Load32, -3)) { - // TODO: Flatten. - out.Write(inst); + addCommonProlog(); + // dest &= (0x00ffffff >> shift) + // Alternatively, could shift to a wall and back (but would require two shifts each way.) + out.WriteSetConstant(IRTEMP_LR_MASK, 0x00ffffff); + out.Write(IROp::Shr, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, inst.dest, inst.dest, IRTEMP_LR_MASK); + // IRTEMP_LR_VALUE <<= (24 - shift) + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + out.Write(IROp::Shl, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE); } break; case IROp::Load32Right: if (!combineOpposite(IROp::Load32Left, 3, IROp::Load32, 0)) { - // TODO: Flatten. - out.Write(inst); + addCommonProlog(); + // IRTEMP_LR_VALUE >>= shift + out.Write(IROp::Shr, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // IRTEMP_LR_SHIFT = 24 - shift + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // dest &= (0xffffff00 << (24 - shift)) + // Alternatively, could shift to a wall and back (but would require two shifts each way.) + out.WriteSetConstant(IRTEMP_LR_MASK, 0xffffff00); + out.Write(IROp::Shl, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, inst.dest, inst.dest, IRTEMP_LR_MASK); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE); } break; case IROp::Store32Left: if (!combineOpposite(IROp::Store32Right, -3, IROp::Store32, -3)) { - // TODO: Flatten. - out.Write(inst); + addCommonProlog(); + // IRTEMP_LR_VALUE &= 0xffffff00 << shift + out.WriteSetConstant(IRTEMP_LR_MASK, 0xffffff00); + out.Write(IROp::Shl, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + // IRTEMP_LR_SHIFT = 24 - shift + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // IRTEMP_LR_VALUE |= src3 >> (24 - shift) + out.Write(IROp::Shr, IRTEMP_LR_MASK, inst.src3, IRTEMP_LR_SHIFT); + out.Write(IROp::Or, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + addCommonStore(0); } break; case IROp::Store32Right: if (!combineOpposite(IROp::Store32Left, 3, IROp::Store32, 0)) { - // TODO: Flatten. - out.Write(inst); + addCommonProlog(); + // IRTEMP_LR_VALUE &= 0x00ffffff << (24 - shift) + out.WriteSetConstant(IRTEMP_LR_MASK, 0x00ffffff); + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + out.Write(IROp::Shr, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); + out.Write(IROp::And, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // IRTEMP_LR_VALUE |= src3 << shift + out.Write(IROp::Shl, IRTEMP_LR_MASK, inst.src3, IRTEMP_LR_SHIFT); + out.Write(IROp::Or, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_MASK); + addCommonStore(0); } break; @@ -289,6 +348,10 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts } if (gpr.IsImm(inst.src1) && gpr.IsImm(inst.src2)) { gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), gpr.GetImm(inst.src2), inst.op)); + } else if (inst.op == IROp::And && gpr.IsImm(inst.src1) && gpr.GetImm(inst.src1) == 0) { + gpr.SetImm(inst.dest, 0); + } else if (inst.op == IROp::And && gpr.IsImm(inst.src2) && gpr.GetImm(inst.src2) == 0) { + gpr.SetImm(inst.dest, 0); } else if (gpr.IsImm(inst.src2)) { const u32 imm2 = gpr.GetImm(inst.src2); gpr.MapDirtyIn(inst.dest, inst.src1); @@ -338,7 +401,10 @@ bool PropagateConstants(const IRWriter &in, IRWriter &out, const IROptions &opts case IROp::XorConst: case IROp::SltConst: case IROp::SltUConst: - if (gpr.IsImm(inst.src1)) { + // And 0 is otherwise set to 0. Happens when optimizing lwl. + if (inst.op == IROp::AndConst && inst.constant == 0) { + gpr.SetImm(inst.dest, 0); + } else if (gpr.IsImm(inst.src1)) { gpr.SetImm(inst.dest, Evaluate(gpr.GetImm(inst.src1), inst.constant, inst.op)); } else { gpr.MapDirtyIn(inst.dest, inst.src1); @@ -657,6 +723,16 @@ int IRDestGPR(const IRInst &inst) { return -1; } +IRInst IRReplaceDestGPR(const IRInst &inst, int fromReg, int toReg) { + IRInst newInst = inst; + const IRMeta *m = GetIRMeta(inst.op); + + if ((m->flags & IRFLAG_SRC3) == 0 && m->types[0] == 'G' && inst.dest == fromReg) { + newInst.dest = toReg; + } + return newInst; +} + bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { std::vector insts; insts.reserve(in.GetInstructions().size()); @@ -715,6 +791,10 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { case IRTEMP_3: case IRTEMP_LHS: case IRTEMP_RHS: + case IRTEMP_LR_ADDR: + case IRTEMP_LR_VALUE: + case IRTEMP_LR_MASK: + case IRTEMP_LR_SHIFT: // Unlike other ops, these don't need to persist between blocks. // So we consider them not read unless proven read. lastWrittenTo[dest] = i; @@ -730,7 +810,7 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { default: lastWrittenTo[dest] = i; - if (dest > IRTEMP_RHS) { + if (dest > IRTEMP_LR_SHIFT) { // These might sometimes be implicitly read/written by other instructions. break; } From fbeedd333b3de0cc98a71e5b4b8e4b060cca6ae4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 20:13:30 -0800 Subject: [PATCH 119/131] irjit: Swap moves when it may allow clobbering. Example: addiu a0, a1, a2 mov s0, a0 addiu a0, a2, a3 By swapping the mov, we can eliminate it. Only going one back because it's common and didn't want to track reads. --- Core/MIPS/IR/IRPassSimplify.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index f365128e44..c0d747d528 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -733,6 +733,11 @@ IRInst IRReplaceDestGPR(const IRInst &inst, int fromReg, int toReg) { return newInst; } +bool IRMutatesDestGPR(const IRInst &inst, int reg) { + const IRMeta *m = GetIRMeta(inst.op); + return (m->flags & IRFLAG_SRC3DST) != 0 && m->types[0] == 'G' && inst.src3 == reg; +} + bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { std::vector insts; insts.reserve(in.GetInstructions().size()); @@ -762,11 +767,18 @@ bool PurgeTemps(const IRWriter &in, IRWriter &out, const IROptions &opts) { if (IRReadsFromGPR(inst, check.reg)) { // Read from, but was this just a copy? - bool mutatesReg = (m->flags & IRFLAG_SRC3DST) != 0 && m->types[0] == 'G' && inst.src3 == check.reg; + bool mutatesReg = IRMutatesDestGPR(inst, check.reg); bool cannotReplace = inst.op == IROp::Interpret || inst.op == IROp::CallReplacement; if (!mutatesReg && !cannotReplace && check.srcReg >= 0 && lastWrittenTo[check.srcReg] < check.index) { // Replace with the srcReg instead. This happens with non-nice delay slots. inst = IRReplaceSrcGPR(inst, check.reg, check.srcReg); + } else if (!IRMutatesDestGPR(insts[check.index], check.reg) && inst.op == IROp::Mov && i == check.index + 1) { + // This happens with lwl/lwr temps. Replace the original dest. + insts[check.index] = IRReplaceDestGPR(insts[check.index], check.reg, inst.dest); + lastWrittenTo[inst.dest] = check.index; + check.reg = inst.dest; + // And swap the args for this mov, since we changed the other dest. We'll optimize this out later. + std::swap(inst.dest, inst.src1); } else { // Legitimately read from, so we can't optimize out. check.reg = 0; From f01e06aefd4d6bbf926f0c6afbb5a8c8f85f368f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 20:44:54 -0800 Subject: [PATCH 120/131] irjit: Improve multiple lwr in a row. --- Core/MIPS/IR/IRPassSimplify.cpp | 54 ++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/IR/IRPassSimplify.cpp b/Core/MIPS/IR/IRPassSimplify.cpp index c0d747d528..fa7481aeff 100644 --- a/Core/MIPS/IR/IRPassSimplify.cpp +++ b/Core/MIPS/IR/IRPassSimplify.cpp @@ -200,18 +200,20 @@ bool ThreeOpToTwoOp(const IRWriter &in, IRWriter &out, const IROptions &opts) { } bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions &opts) { - IRInst dummy{ IROp::Nop }; - bool logBlocks = false; for (int i = 0, n = (int)in.GetInstructions().size(); i < n; ++i) { const IRInst &inst = in.GetInstructions()[i]; - const IRInst &next = i + 1 < n ? in.GetInstructions()[i + 1] : dummy; // TODO: Reorder or look ahead to combine? + auto nextOp = [&]() -> const IRInst &{ + return in.GetInstructions()[i + 1]; + }; + auto combineOpposite = [&](IROp matchOp, int matchOff, IROp replaceOp, int replaceOff) { - if (!opts.unalignedLoadStore) + if (!opts.unalignedLoadStore || i + 1 >= n) return false; + const IRInst &next = nextOp(); if (next.op != matchOp || next.dest != inst.dest || next.src1 != inst.src1) return false; if (inst.constant + matchOff != next.constant) @@ -249,12 +251,29 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions out.WriteSetConstant(IRTEMP_LR_MASK, 0x00ffffff); out.Write(IROp::Shr, IRTEMP_LR_MASK, IRTEMP_LR_MASK, IRTEMP_LR_SHIFT); out.Write(IROp::And, inst.dest, inst.dest, IRTEMP_LR_MASK); - // IRTEMP_LR_VALUE <<= (24 - shift) + // IRTEMP_LR_SHIFT = 24 - shift out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + // IRTEMP_LR_VALUE <<= (24 - shift) out.Write(IROp::Shl, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); // dest |= IRTEMP_LR_VALUE out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE); + + bool src1Dirty = inst.dest == inst.src1; + while (i + 1 < n && !src1Dirty && nextOp().op == inst.op && nextOp().src1 == inst.src1 && (nextOp().constant & 3) == (inst.constant & 3)) { + // IRTEMP_LR_VALUE = RAM(IRTEMP_LR_ADDR + offsetDelta) + out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(nextOp().constant - inst.constant)); + + // dest &= IRTEMP_LR_MASK + out.Write(IROp::And, nextOp().dest, nextOp().dest, IRTEMP_LR_MASK); + // IRTEMP_LR_VALUE <<= (24 - shift) + out.Write(IROp::Shl, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, nextOp().dest, nextOp().dest, IRTEMP_LR_VALUE); + + src1Dirty = nextOp().dest == inst.src1; + ++i; + } } break; @@ -273,6 +292,31 @@ bool RemoveLoadStoreLeftRight(const IRWriter &in, IRWriter &out, const IROptions out.Write(IROp::And, inst.dest, inst.dest, IRTEMP_LR_MASK); // dest |= IRTEMP_LR_VALUE out.Write(IROp::Or, inst.dest, inst.dest, IRTEMP_LR_VALUE); + + // Building display lists sometimes involves a bunch of lwr in a row. + // We can generate more optimal code by combining. + bool shiftNeedsReverse = true; + bool src1Dirty = inst.dest == inst.src1; + while (i + 1 < n && !src1Dirty && nextOp().op == inst.op && nextOp().src1 == inst.src1 && (nextOp().constant & 3) == (inst.constant & 3)) { + // IRTEMP_LR_VALUE = RAM(IRTEMP_LR_ADDR + offsetDelta) + out.Write(IROp::Load32, IRTEMP_LR_VALUE, IRTEMP_LR_ADDR, out.AddConstant(nextOp().constant - inst.constant)); + + if (shiftNeedsReverse) { + // IRTEMP_LR_SHIFT = shift again + out.Write(IROp::Neg, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT); + out.Write(IROp::AddConst, IRTEMP_LR_SHIFT, IRTEMP_LR_SHIFT, out.AddConstant(24)); + shiftNeedsReverse = false; + } + // IRTEMP_LR_VALUE >>= IRTEMP_LR_SHIFT + out.Write(IROp::Shr, IRTEMP_LR_VALUE, IRTEMP_LR_VALUE, IRTEMP_LR_SHIFT); + // dest &= IRTEMP_LR_MASK + out.Write(IROp::And, nextOp().dest, nextOp().dest, IRTEMP_LR_MASK); + // dest |= IRTEMP_LR_VALUE + out.Write(IROp::Or, nextOp().dest, nextOp().dest, IRTEMP_LR_VALUE); + + src1Dirty = nextOp().dest == inst.src1; + ++i; + } } break; From 544344f5058c8c09c340d4335ad3e6320a782e5d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 7 Jan 2018 21:03:47 -0800 Subject: [PATCH 121/131] GPU: Improve some bezier logging. Meant to do this when splines were changed. --- GPU/GPUCommon.cpp | 7 ++----- GPU/Software/SoftGpu.cpp | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index 0e7e8c2296..af99023ced 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -1458,11 +1458,8 @@ void GPUCommon::Execute_Spline(u32 op, u32 diff) { indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); } - if (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) { - DEBUG_LOG_REPORT(G3D, "Spline + morph: %i", (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT); - } - if (vertTypeIsSkinningEnabled(gstate.vertType)) { - DEBUG_LOG_REPORT(G3D, "Spline + skinning: %i", vertTypeGetNumBoneWeights(gstate.vertType)); + if ((gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(gstate.vertType)) { + DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); } int sp_ucount = op & 0xFF; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index bd14ab1e2f..b490659829 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -443,11 +443,8 @@ void SoftGPU::ExecuteOp(u32 op, u32 diff) { indices = Memory::GetPointerUnchecked(gstate_c.indexAddr); } - if (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) { - DEBUG_LOG_REPORT(G3D, "Spline + morph: %i", (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT); - } - if (vertTypeIsSkinningEnabled(gstate.vertType)) { - DEBUG_LOG_REPORT(G3D, "Spline + skinning: %i", vertTypeGetNumBoneWeights(gstate.vertType)); + if ((gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) || vertTypeIsSkinningEnabled(gstate.vertType)) { + DEBUG_LOG_REPORT(G3D, "Unusual bezier/spline vtype: %08x, morph: %d, bones: %d", gstate.vertType, (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) >> GE_VTYPE_MORPHCOUNT_SHIFT, vertTypeGetNumBoneWeights(gstate.vertType)); } int sp_ucount = op & 0xFF; From 6073826ca0ae2f0092ed137df48ddda87582f123 Mon Sep 17 00:00:00 2001 From: vnctdj Date: Wed, 10 Jan 2018 00:38:16 +0100 Subject: [PATCH 122/131] Use more specific words when applicable --- UI/DevScreens.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 264ca0c562..47f0d3a11b 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -366,7 +366,7 @@ void SystemInfoScreen::CreateViews() { deviceSpecs->Add(new InfoItem(si->T("ABI"), GetCompilerABI())); #ifdef _WIN32 if (IsDebuggerPresent()) { - deviceSpecs->Add(new InfoItem(si->T("Debugger Present"), si->T("Yes"))); + deviceSpecs->Add(new InfoItem(si->T("Debugger Present"), di->T("Yes"))); } #endif @@ -400,13 +400,13 @@ void SystemInfoScreen::CreateViews() { #endif #endif if (GetGPUBackend() == GPUBackend::OPENGL) { - deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? si->T("Yes") : si->T("No"))); + deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? di->T("Supported") : di->T("Unsupported"))); } deviceSpecs->Add(new ItemHeader(si->T("OS Information"))); deviceSpecs->Add(new InfoItem(si->T("Memory Page Size"), StringFromFormat(si->T("%d bytes"), GetMemoryProtectPageSize()))); - deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? si->T("Yes") : si->T("No"))); + deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? di->T("Yes") : di->T("No"))); #ifdef ANDROID - deviceSpecs->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? si->T("Yes") : si->T("No"))); + deviceSpecs->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? di->T("Supported") : di->T("Unsupported"))); #endif const char *build = si->T("Release"); From b6bb0159e3a8eb4b646aa1f11c9e3c088c564122 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 9 Jan 2018 18:06:25 -0800 Subject: [PATCH 123/131] irjit: Remove Comp_ITypeMemLR. --- Core/MIPS/IR/IRCompLoadStore.cpp | 6 ------ Core/MIPS/IR/IRFrontend.h | 3 --- 2 files changed, 9 deletions(-) diff --git a/Core/MIPS/IR/IRCompLoadStore.cpp b/Core/MIPS/IR/IRCompLoadStore.cpp index 14dcbe45be..4ef50d5e0b 100644 --- a/Core/MIPS/IR/IRCompLoadStore.cpp +++ b/Core/MIPS/IR/IRCompLoadStore.cpp @@ -44,12 +44,6 @@ #define INVALIDOP { Comp_Generic(op); return; } namespace MIPSComp { - void IRFrontend::Comp_ITypeMemLR(MIPSOpcode op, bool load) { - CONDITIONAL_DISABLE; - // Should never get here, done in Comp_ITypeMem(). - INVALIDOP; - } - void IRFrontend::Comp_ITypeMem(MIPSOpcode op) { CONDITIONAL_DISABLE; diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index 70194cef1b..5a9064ec24 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -132,9 +132,6 @@ private: void GetVectorRegs(u8 regs[4], VectorSize N, int vectorReg); void GetMatrixRegs(u8 regs[16], MatrixSize N, int matrixReg); - // Utils - void Comp_ITypeMemLR(MIPSOpcode op, bool load); - // State JitState js; IRWriter ir; From a4df576f7c146f05a63b89e7ad2bb2ce9a31d966 Mon Sep 17 00:00:00 2001 From: vnctdj Date: Wed, 10 Jan 2018 08:57:41 +0100 Subject: [PATCH 124/131] Use more accurate words --- UI/DevScreens.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index 47f0d3a11b..45870eda72 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -400,11 +400,11 @@ void SystemInfoScreen::CreateViews() { #endif #endif if (GetGPUBackend() == GPUBackend::OPENGL) { - deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? di->T("Supported") : di->T("Unsupported"))); + deviceSpecs->Add(new InfoItem(si->T("Core Context"), gl_extensions.IsCoreContext ? di->T("Active") : di->T("Inactive"))); } deviceSpecs->Add(new ItemHeader(si->T("OS Information"))); deviceSpecs->Add(new InfoItem(si->T("Memory Page Size"), StringFromFormat(si->T("%d bytes"), GetMemoryProtectPageSize()))); - deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? di->T("Yes") : di->T("No"))); + deviceSpecs->Add(new InfoItem(si->T("RW/RX exclusive"), PlatformIsWXExclusive() ? di->T("Active") : di->T("Inactive"))); #ifdef ANDROID deviceSpecs->Add(new InfoItem(si->T("Sustained perf mode"), System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE) ? di->T("Supported") : di->T("Unsupported"))); #endif From fb0e81484bdb32f6480c763ce23802c9ffe68777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 9 Jan 2018 14:08:09 +0100 Subject: [PATCH 125/131] Minor cleanup --- Core/MIPS/IR/IRCompALU.cpp | 8 ++++---- Core/MIPS/IR/IRFrontend.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/IR/IRCompALU.cpp b/Core/MIPS/IR/IRCompALU.cpp index 31dba9bfb9..70fa758dac 100644 --- a/Core/MIPS/IR/IRCompALU.cpp +++ b/Core/MIPS/IR/IRCompALU.cpp @@ -188,7 +188,7 @@ void IRFrontend::CompShiftImm(MIPSOpcode op, IROp shiftOpImm, int sa) { ir.Write(shiftOpImm, rd, rt, sa); } -void IRFrontend::CompShiftVar(MIPSOpcode op, IROp shiftOp, IROp shiftOpImm) { +void IRFrontend::CompShiftVar(MIPSOpcode op, IROp shiftOp) { MIPSGPReg rd = _RD; MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; @@ -211,9 +211,9 @@ void IRFrontend::Comp_ShiftType(MIPSOpcode op) { case 0: CompShiftImm(op, IROp::ShlImm, sa); break; //sll case 2: CompShiftImm(op, (rs == 1 ? IROp::RorImm : IROp::ShrImm), sa); break; //srl case 3: CompShiftImm(op, IROp::SarImm, sa); break; //sra - case 4: CompShiftVar(op, IROp::Shl, IROp::ShlImm); break; //sllv - case 6: CompShiftVar(op, (sa == 1 ? IROp::Ror : IROp::Shr), (sa == 1 ? IROp::RorImm : IROp::ShrImm)); break; //srlv - case 7: CompShiftVar(op, IROp::Sar, IROp::SarImm); break; //srav + case 4: CompShiftVar(op, IROp::Shl); break; //sllv + case 6: CompShiftVar(op, (sa == 1 ? IROp::Ror : IROp::Shr)); break; //srlv + case 7: CompShiftVar(op, IROp::Sar); break; //srav default: INVALIDOP; diff --git a/Core/MIPS/IR/IRFrontend.h b/Core/MIPS/IR/IRFrontend.h index 50061aa523..0f9a1ab418 100644 --- a/Core/MIPS/IR/IRFrontend.h +++ b/Core/MIPS/IR/IRFrontend.h @@ -122,7 +122,7 @@ private: // Utilities to reduce duplicated code void CompShiftImm(MIPSOpcode op, IROp shiftType, int sa); - void CompShiftVar(MIPSOpcode op, IROp shiftType, IROp shiftTypeConst); + void CompShiftVar(MIPSOpcode op, IROp shiftType); void ApplyPrefixST(u8 *vregs, u32 prefix, VectorSize sz, int tempReg); void ApplyPrefixD(const u8 *vregs, VectorSize sz); From 34f79904fd71e2c45028dcf70cc64bd4d75744c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 10 Jan 2018 09:18:12 +0100 Subject: [PATCH 126/131] IR: This optimization is safe when all three regs are consecutive, so avoid disabling it unnecessarily. --- Core/MIPS/IR/IRCompVFPU.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 82ecffc1da..63fe98b033 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -617,17 +617,16 @@ namespace MIPSComp { GetVectorRegsPrefixD(dregs, sz, _VD); u8 tempregs[4]; - bool usingTemps = false; for (int i = 0; i < n; i++) { if (!IsOverlapSafe(dregs[i], n, sregs, n, tregs)) { tempregs[i] = IRVTEMP_0 + i; - usingTemps = true; } else { tempregs[i] = dregs[i]; } } - if (allowSIMD && sz == V_Quad && !usingTemps && IsConsecutive4(dregs) && IsConsecutive4(sregs) && IsConsecutive4(tregs)) { + // If all three are consecutive 4, we're safe regardless of if we use temps so we should not check that here. + if (allowSIMD && sz == V_Quad && IsConsecutive4(dregs) && IsConsecutive4(sregs) && IsConsecutive4(tregs)) { IROp opFunc = IROp::Nop; switch (op >> 26) { case 24: //VFPU0 From d3f8a4db0531b97d449aa1ba446a5057f6e500bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 11 Jan 2018 16:56:11 +0100 Subject: [PATCH 127/131] Update lang submodule --- assets/lang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/lang b/assets/lang index 1e3e4a0ba0..350f71780a 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit 1e3e4a0ba0ca8c0a092e027dfb7c1c4778366db5 +Subproject commit 350f71780adaaec0261d68149c093c6e1d86e215 From 025c382b9bdf91c239a5c04394986b25dad9112f Mon Sep 17 00:00:00 2001 From: Greg V Date: Fri, 12 Jan 2018 22:18:58 +0300 Subject: [PATCH 128/131] Check for Vulkan Xlib/Wayland surface support before enabling The Vulkan loader will error if a requested extension is not available, and the nvidia proprietary driver does not support the Wayland one. --- Common/Vulkan/VulkanContext.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Common/Vulkan/VulkanContext.cpp b/Common/Vulkan/VulkanContext.cpp index 3bf537090f..70448a0905 100644 --- a/Common/Vulkan/VulkanContext.cpp +++ b/Common/Vulkan/VulkanContext.cpp @@ -111,7 +111,9 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { instance_extensions_enabled_.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); #else #if defined(VK_USE_PLATFORM_XLIB_KHR) - instance_extensions_enabled_.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + if (IsInstanceExtensionAvailable(VK_KHR_XLIB_SURFACE_EXTENSION_NAME)) { + instance_extensions_enabled_.push_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + } #endif //#if defined(VK_USE_PLATFORM_XCB_KHR) // instance_extensions_enabled_.push_back(VK_KHR_XCB_SURFACE_EXTENSION_NAME); @@ -120,7 +122,9 @@ VkResult VulkanContext::CreateInstance(const CreateInfo &info) { // instance_extensions_enabled_.push_back(VK_KHR_MIR_SURFACE_EXTENSION_NAME); //#endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) - instance_extensions_enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + if (IsInstanceExtensionAvailable(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME)) { + instance_extensions_enabled_.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); + } #endif #endif From bb4b1e7dc9d3ddf3ea53bd19f357c1bb524153d3 Mon Sep 17 00:00:00 2001 From: Florin9doi Date: Fri, 12 Jan 2018 22:36:02 +0200 Subject: [PATCH 129/131] Update Inno installer --- ppsspp.iss | 73 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/ppsspp.iss b/ppsspp.iss index f1b3c5bd72..9d37d59206 100644 --- a/ppsspp.iss +++ b/ppsspp.iss @@ -1,21 +1,73 @@ +#define ApplicationName 'PPSSPP' +#define ApplicationVersion GetFileVersion('PPSSPPWindows.exe') + +[Languages] +Name: "en"; MessagesFile: "compiler:Default.isl" +Name: "ar"; MessagesFile: "compiler:Languages\Armenian.islu" +Name: "br"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl" +Name: "ca"; MessagesFile: "compiler:Languages\Catalan.isl" +Name: "co"; MessagesFile: "compiler:Languages\Corsican.isl" +Name: "cz"; MessagesFile: "compiler:Languages\Czech.isl" +Name: "da"; MessagesFile: "compiler:Languages\Danish.isl" +Name: "du"; MessagesFile: "compiler:Languages\Dutch.isl" +Name: "fi"; MessagesFile: "compiler:Languages\Finnish.isl" +Name: "fr"; MessagesFile: "compiler:Languages\French.isl" +Name: "ge"; MessagesFile: "compiler:Languages\German.isl" +Name: "gr"; MessagesFile: "compiler:Languages\Greek.isl" +Name: "he"; MessagesFile: "compiler:Languages\Hebrew.isl" +Name: "hu"; MessagesFile: "compiler:Languages\Hungarian.isl" +Name: "it"; MessagesFile: "compiler:Languages\Italian.isl" +Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl" +Name: "ne"; MessagesFile: "compiler:Languages\Nepali.islu" +Name: "no"; MessagesFile: "compiler:Languages\Norwegian.isl" +Name: "pl"; MessagesFile: "compiler:Languages\Polish.isl" +Name: "po"; MessagesFile: "compiler:Languages\Portuguese.isl" +Name: "ru"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "sc"; MessagesFile: "compiler:Languages\ScottishGaelic.isl" +Name: "sec"; MessagesFile: "compiler:Languages\SerbianCyrillic.isl" +Name: "ser"; MessagesFile: "compiler:Languages\SerbianLatin.isl" +Name: "sl"; MessagesFile: "compiler:Languages\Slovenian.isl" +Name: "sp"; MessagesFile: "compiler:Languages\Spanish.isl" +Name: "tr"; MessagesFile: "compiler:Languages\Turkish.isl" +Name: "uk"; MessagesFile: "compiler:Languages\Ukrainian.isl" +; more languages are available at: http://www.jrsoftware.org/files/istrans/ +; but those need to be mirrored on PPSSPP repo somehow + [Setup] +; Installer - name for title and wizard pages +AppName={#ApplicationName} +; Installer - version +VersionInfoVersion={#ApplicationVersion} + +; Programs and Features - Name +AppVerName={#ApplicationName} +; Programs and Features - Version AppVersion={#ApplicationVersion} -AppName={cm:AppName} +; Programs and Features - Publisher +AppPublisher=PPSSPP Team +; Programs and Features - Help link +AppSupportURL=https://forums.ppsspp.org +; Programs and Features - Support link +AppPublisherURL=https://www.ppsspp.org +; Programs and Features - Update information +AppUpdatesURL=https://www.ppsspp.org/downloads.html + DefaultDirName={pf}\PPSSPP ; Since no icons will be created in "{group}", we don't need the wizard ; to ask for a Start Menu folder name: DisableProgramGroupPage=yes UninstallDisplayIcon={app}\PPSSPPWindows.exe -OutputDir=. -UsePreviousLanguage=no +; Detect the correct directory for Program Files x86/64 +ArchitecturesInstallIn64BitMode=x64 -[CustomMessages] -AppName={#ApplicationName} -LaunchProgram=Start {#ApplicationName} after finishing installation +; Save the installer as ... +OutputBaseFilename={#ApplicationName}_{#ApplicationVersion}_Setup +; ... in the same directory +OutputDir=. [Files] Source: "PPSSPPWindows.exe"; DestDir: "{app}" -Source: "PPSSPPWindows64.exe"; DestDir: "{app}" +Source: "PPSSPPWindows64.exe"; DestDir: "{app}"; Check: IsWin64 Source: "README.md"; DestName: "README.txt"; DestDir: "{app}"; Flags: isreadme ; Special file to signify that we are installed, and not "portable" and should look for ; configuration etc in the user's directory, not in our own subdirectory. @@ -28,7 +80,10 @@ Source: "dx9sdk\8.1\Redist\D3D\x64\d3dcompiler_47.dll"; DestDir: "{app}" Source: "dx9sdk\8.1\Redist\D3D\x86\d3dcompiler_47.dll"; DestName: "d3dcompiler_47.x86.dll"; DestDir: "{app}" [Run] -Filename: {app}\PPSSPPWindows.exe; Description: {cm:LaunchProgram,{cm:AppName}}; Flags: nowait postinstall skipifsilent +Filename: {app}\PPSSPPWindows.exe; Description: {cm:LaunchProgram,{#ApplicationName}}; Flags: nowait postinstall skipifsilent; Check: not IsWin64 +Filename: {app}\PPSSPPWindows64.exe; Description: {cm:LaunchProgram,{#ApplicationName}}; Flags: nowait postinstall skipifsilent; Check: IsWin64 [Icons] -Name: "{commonprograms}\PPSSPP"; Filename: "{app}\PPSSPPWindows.exe" +Name: "{commonprograms}\PPSSPP"; Filename: "{app}\PPSSPPWindows.exe"; Check: not IsWin64 +Name: "{commonprograms}\PPSSPP"; Filename: "{app}\PPSSPPWindows64.exe"; Check: IsWin64 + From fb34cb5d55455c9ccb77e13852054763adabed70 Mon Sep 17 00:00:00 2001 From: vnctdj Date: Sat, 13 Jan 2018 21:44:58 +0100 Subject: [PATCH 130/131] Don't prevent use of make's long options + an indentation fix --- b.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/b.sh b/b.sh index a001421eac..c0cc2115ab 100755 --- a/b.sh +++ b/b.sh @@ -39,13 +39,10 @@ do --no-package) echo "Packaging disabled" PACKAGE=0 ;; - --clang) echo "Clang enabled" + --clang) echo "Clang enabled" export CC=/usr/bin/clang export CXX=/usr/bin/clang++ ;; - --*) echo "Bad option: $1" - exit 1 - ;; *) MAKE_OPT="$1 ${MAKE_OPT}" ;; esac From 0e474077e4cb5044471c2c5e6c884ec2934506a8 Mon Sep 17 00:00:00 2001 From: Conrad Kreyling Date: Sun, 14 Jan 2018 13:38:04 -0500 Subject: [PATCH 131/131] Add taptic feedback on virtual buttons --- ios/PPSSPPUIApplication.h | 3 +++ ios/main.mm | 48 +++++++++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/ios/PPSSPPUIApplication.h b/ios/PPSSPPUIApplication.h index e7af973f14..cd927c7d01 100644 --- a/ios/PPSSPPUIApplication.h +++ b/ios/PPSSPPUIApplication.h @@ -14,6 +14,9 @@ @interface PPSSPPUIApplication : UIApplication { } + +@property (nonatomic, strong) UISelectionFeedbackGenerator *feedbackGenerator; + @end diff --git a/ios/main.mm b/ios/main.mm index 27f9cbac71..8a5817982a 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -98,15 +98,45 @@ PermissionStatus System_GetPermissionStatus(SystemPermission permission) { retur FOUNDATION_EXTERN void AudioServicesPlaySystemSoundWithVibration(unsigned long, objc_object*, NSDictionary*); -void Vibrate(int length_ms) { - NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; - NSArray *pattern = @[@YES, @30, @NO, @2]; - - dictionary[@"VibePattern"] = pattern; - dictionary[@"Intensity"] = @2; - - AudioServicesPlaySystemSoundWithVibration(kSystemSoundID_Vibrate, nil, dictionary); - // TODO: Actually make use of length_ms if PPSSPP ever adds that in the config +BOOL SupportsTaptic() +{ + // we're on an iOS version that cannot instantiate UISelectionFeedbackGenerator, so no. + if(!NSClassFromString(@"UISelectionFeedbackGenerator")) + { + return NO; + } + + // http://www.mikitamanko.com/blog/2017/01/29/haptic-feedback-with-uifeedbackgenerator/ + // use private API against UIDevice to determine the haptic stepping + // 2 - iPhone 7 or above, full taptic feedback + // 1 - iPhone 6S, limited taptic feedback + // 0 - iPhone 6 or below, no taptic feedback + NSNumber* val = (NSNumber*)[[UIDevice currentDevice] valueForKey:@"feedbackSupportLevel"]; + return [val intValue] >= 2; +} + +void Vibrate(int mode) { + + if(SupportsTaptic()) + { + PPSSPPUIApplication* app = (PPSSPPUIApplication*)[UIApplication sharedApplication]; + if(app.feedbackGenerator == nil) + { + app.feedbackGenerator = [[UISelectionFeedbackGenerator alloc] init]; + [app.feedbackGenerator prepare]; + } + [app.feedbackGenerator selectionChanged]; + } + else + { + NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; + NSArray *pattern = @[@YES, @30, @NO, @2]; + + dictionary[@"VibePattern"] = pattern; + dictionary[@"Intensity"] = @2; + + AudioServicesPlaySystemSoundWithVibration(kSystemSoundID_Vibrate, nil, dictionary); + } } int main(int argc, char *argv[])