diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h index 066fe40a0d..1e660452f4 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h @@ -237,6 +237,14 @@ class JitBase : public CPUCoreBase virtual const CommonAsmRoutinesBase* GetAsmRoutines() = 0; + // The core that compiled the block a runtime hook fired from, and therefore + // the one whose js sets and block cache a registration belongs in. That is + // normally this core. A core that mostly executes prebuilt code but keeps a + // JIT for whatever it does not cover must name that JIT instead: the JIT is + // what reads those sets at compile time, and the only thing that can retire + // the hook by recompiling the block with the check folded in. + virtual JitBase* GetExceptionCheckTarget() { return this; } + virtual bool WantsPageTableMappings() const; virtual bool HandleFault(uintptr_t access_address, SContext* ctx) = 0; diff --git a/Source/Core/Core/PowerPC/JitInterface.cpp b/Source/Core/Core/PowerPC/JitInterface.cpp index e7385fce17..d098c19a64 100644 --- a/Source/Core/Core/PowerPC/JitInterface.cpp +++ b/Source/Core/Core/PowerPC/JitInterface.cpp @@ -322,18 +322,24 @@ void JitInterface::CompileExceptionCheck(ExceptionType type) if (!m_jit) return; + // Register against the core that compiled the block this fired from. Under + // static recompilation that is the fallback JIT rather than the static core: + // the fallback is what reads these sets when it compiles, and what retires + // the call site by recompiling with the check folded inline. + JitBase* const target = m_jit->GetExceptionCheckTarget(); + std::unordered_set* exception_addresses = nullptr; switch (type) { case ExceptionType::FIFOWrite: - exception_addresses = &m_jit->js.fifoWriteAddresses; + exception_addresses = &target->js.fifoWriteAddresses; break; case ExceptionType::PairedQuantize: - exception_addresses = &m_jit->js.pairedQuantizeAddresses; + exception_addresses = &target->js.pairedQuantizeAddresses; break; case ExceptionType::SpeculativeConstants: - exception_addresses = &m_jit->js.noSpeculativeConstantsAddresses; + exception_addresses = &target->js.noSpeculativeConstantsAddresses; break; } @@ -355,8 +361,9 @@ void JitInterface::CompileExceptionCheck(ExceptionType type) exception_addresses->insert(ppc_state.pc); // Invalidate the JIT block so that it gets recompiled with the external exception check - // included. - m_jit->GetBlockCache()->InvalidateICache(ppc_state.pc, 4, true); + // included. Same core as the set above: invalidating a different core's + // cache leaves the block standing and the hook firing. + target->GetBlockCache()->InvalidateICache(ppc_state.pc, 4, true); } } diff --git a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h index 44d027bef7..d084fea634 100644 --- a/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h +++ b/Source/Core/Core/PowerPC/StaticRecomp/StaticRecompCore.h @@ -61,6 +61,17 @@ class StaticRecompCore : public JitBase bool HandleFault(uintptr_t access_address, SContext* ctx) override { return false; } JitBaseBlockCache* GetBlockCache() override { return &m_block_cache; } + + // Gather-pipe and quantize hooks do not only fire from module code: anything + // the module does not cover runs on m_fallback_jit, a real Jit64/JitArm64. + // That core reads fifoWriteAddresses at compile time and owns the block that + // has to be invalidated, so registering against this core instead left the + // check uncompiled and the hook firing on every gather-pipe store forever. + JitBase* GetExceptionCheckTarget() override + { + return m_fallback_jit ? m_fallback_jit.get() : this; + } + void EraseSingleBlock(const JitBlock& block) override {} std::vector GetMemoryStats() const override { return {}; } std::size_t DisassembleNearCode(const JitBlock& block, std::ostream& stream) const override