From 312790c9a6c4f4f04e315a4a4747ecee20fe205c Mon Sep 17 00:00:00 2001
From: Stenzek <stenzek@gmail.com>
Date: Thu, 9 Nov 2023 02:10:39 +1000
Subject: [PATCH] CPU/NewRec: Handle mtc0 rt, sr

---
 data/resources/gamedb.json                 |  7 +---
 src/core/cpu_core.cpp                      |  7 ++++
 src/core/cpu_newrec_compiler_aarch32.cpp   | 48 ++++++++++++++++-----
 src/core/cpu_newrec_compiler_aarch32.h     |  2 +-
 src/core/cpu_newrec_compiler_aarch64.cpp   | 46 +++++++++++++++-----
 src/core/cpu_newrec_compiler_aarch64.h     |  2 +-
 src/core/cpu_newrec_compiler_riscv64.cpp   | 46 +++++++++++++++-----
 src/core/cpu_newrec_compiler_riscv64.h     |  2 +-
 src/core/cpu_newrec_compiler_x64.cpp       | 49 ++++++++++++++++------
 src/core/cpu_newrec_compiler_x64.h         |  2 +-
 src/core/cpu_recompiler_code_generator.cpp |  7 ++++
 11 files changed, 163 insertions(+), 55 deletions(-)

diff --git a/data/resources/gamedb.json b/data/resources/gamedb.json
index ea1f0f166..5910835b3 100644
--- a/data/resources/gamedb.json
+++ b/data/resources/gamedb.json
@@ -200045,7 +200045,6 @@
    }
   ],
   "traits": {
-   "ForceInterpreter": true,
    "IsLibCryptProtected": true
   }
  },
@@ -200084,11 +200083,7 @@
    }
   ],
   "compatibility": {
-   "rating": 5,
-   "comments": "Must use interpreter to avoid crash"
-  },
-  "traits": {
-   "ForceInterpreter": true
+   "rating": 5
   }
  },
  {
diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp
index c3500143c..fc9244355 100644
--- a/src/core/cpu_core.cpp
+++ b/src/core/cpu_core.cpp
@@ -2357,6 +2357,13 @@ void CPU::CodeCache::InterpretUncachedBlock()
     {
       break;
     }
+    else if ((g_state.current_instruction.bits & 0xFFC0FFFFu) == 0x40806000u && HasPendingInterrupt())
+    {
+      // mtc0 rt, sr - Jackie Chan Stuntmaster, MTV Sports games.
+      // Pain in the ass games trigger a software interrupt by writing to SR.Im.
+      break;
+    }
+
 
     in_branch_delay_slot = branch;
   }
diff --git a/src/core/cpu_newrec_compiler_aarch32.cpp b/src/core/cpu_newrec_compiler_aarch32.cpp
index 3a63cc8bd..b699396c5 100644
--- a/src/core/cpu_newrec_compiler_aarch32.cpp
+++ b/src/core/cpu_newrec_compiler_aarch32.cpp
@@ -367,14 +367,14 @@ void CPU::NewRec::AArch32Compiler::EndBlock(const std::optional<u32>& newpc, boo
 
   // flush regs
   Flush(FLUSH_END_BLOCK);
-  EndAndLinkBlock(newpc, do_event_test);
+  EndAndLinkBlock(newpc, do_event_test, false);
 }
 
 void CPU::NewRec::AArch32Compiler::EndBlockWithException(Exception excode)
 {
   // flush regs, but not pc, it's going to get overwritten
   // flush cycles because of the GTE instruction stuff...
-  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
 
   // TODO: flush load delay
   // TODO: break for pcdrv
@@ -385,14 +385,16 @@ void CPU::NewRec::AArch32Compiler::EndBlockWithException(Exception excode)
   EmitCall(reinterpret_cast<const void*>(static_cast<void (*)(u32, u32)>(&CPU::RaiseException)));
   m_dirty_pc = false;
 
-  EndAndLinkBlock(std::nullopt, true);
+  EndAndLinkBlock(std::nullopt, true, false);
 }
 
-void CPU::NewRec::AArch32Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test)
+void CPU::NewRec::AArch32Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test,
+                                                   bool force_run_events)
 {
   // event test
   // pc should've been flushed
-  DebugAssert(!m_dirty_pc);
+  DebugAssert(!m_dirty_pc && !force_run_events);
+  m_block_ended = true;
 
   // TODO: try extracting this to a function
 
@@ -420,7 +422,11 @@ void CPU::NewRec::AArch32Compiler::EndAndLinkBlock(const std::optional<u32>& new
     armEmitCondBranch(armAsm, ge, CodeCache::g_run_events_and_dispatch);
 
   // jump to dispatcher or next block
-  if (!newpc.has_value())
+  if (force_run_events)
+  {
+    armEmitJmp(armAsm, CodeCache::g_run_events_and_dispatch, false);
+  }
+  else if (!newpc.has_value())
   {
     armEmitJmp(armAsm, CodeCache::g_dispatcher, false);
   }
@@ -438,8 +444,6 @@ void CPU::NewRec::AArch32Compiler::EndAndLinkBlock(const std::optional<u32>& new
       armEmitJmp(armAsm, target, true);
     }
   }
-
-  m_block_ended = true;
 }
 
 const void* CPU::NewRec::AArch32Compiler::EndCompile(u32* code_size, u32* far_code_size)
@@ -1979,9 +1983,31 @@ void CPU::NewRec::AArch32Compiler::TestInterrupts(const vixl::aarch32::Register&
 
   SwitchToFarCode(true, ne);
   BackupHostState();
-  Flush(FLUSH_FLUSH_MIPS_REGISTERS | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
-  EmitCall(reinterpret_cast<const void*>(&DispatchInterrupt));
-  EndBlock(std::nullopt, true);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
+
+  // Can't use EndBlockWithException() here, because it'll use the wrong PC.
+  // Can't use RaiseException() on the fast path if we're the last instruction, because the next PC is unknown.
+  if (!iinfo->is_last_instruction)
+  {
+    EmitMov(RARG1, Cop0Registers::CAUSE::MakeValueForException(Exception::INT, iinfo->is_branch_instruction, false,
+                                                               (inst + 1)->cop.cop_n));
+    EmitMov(RARG2, m_compiler_pc);
+    EmitCall(reinterpret_cast<const void*>(static_cast<void (*)(u32, u32)>(&CPU::RaiseException)));
+    m_dirty_pc = false;
+    EndAndLinkBlock(std::nullopt, true, false);
+  }
+  else
+  {
+    EmitMov(RARG1, 0);
+    if (m_dirty_pc)
+      EmitMov(RARG2, m_compiler_pc);
+    armAsm->str(RARG1, PTR(&g_state.downcount));
+    if (m_dirty_pc)
+      armAsm->str(RARG2, m_compiler_pc);
+    m_dirty_pc = false;
+    EndAndLinkBlock(std::nullopt, false, true);
+  }
+
   RestoreHostState();
   SwitchToNearCode(false);
 
diff --git a/src/core/cpu_newrec_compiler_aarch32.h b/src/core/cpu_newrec_compiler_aarch32.h
index cffc40069..43be93b55 100644
--- a/src/core/cpu_newrec_compiler_aarch32.h
+++ b/src/core/cpu_newrec_compiler_aarch32.h
@@ -35,7 +35,7 @@ protected:
   void GenerateCall(const void* func, s32 arg1reg = -1, s32 arg2reg = -1, s32 arg3reg = -1) override;
   void EndBlock(const std::optional<u32>& newpc, bool do_event_test) override;
   void EndBlockWithException(Exception excode) override;
-  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test);
+  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test, bool force_run_events);
   const void* EndCompile(u32* code_size, u32* far_code_size) override;
 
   void Flush(u32 flags) override;
diff --git a/src/core/cpu_newrec_compiler_aarch64.cpp b/src/core/cpu_newrec_compiler_aarch64.cpp
index af195b314..df0398633 100644
--- a/src/core/cpu_newrec_compiler_aarch64.cpp
+++ b/src/core/cpu_newrec_compiler_aarch64.cpp
@@ -339,14 +339,14 @@ void CPU::NewRec::AArch64Compiler::EndBlock(const std::optional<u32>& newpc, boo
 
   // flush regs
   Flush(FLUSH_END_BLOCK);
-  EndAndLinkBlock(newpc, do_event_test);
+  EndAndLinkBlock(newpc, do_event_test, false);
 }
 
 void CPU::NewRec::AArch64Compiler::EndBlockWithException(Exception excode)
 {
   // flush regs, but not pc, it's going to get overwritten
   // flush cycles because of the GTE instruction stuff...
-  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
 
   // TODO: flush load delay
   // TODO: break for pcdrv
@@ -357,14 +357,16 @@ void CPU::NewRec::AArch64Compiler::EndBlockWithException(Exception excode)
   EmitCall(reinterpret_cast<const void*>(static_cast<void (*)(u32, u32)>(&CPU::RaiseException)));
   m_dirty_pc = false;
 
-  EndAndLinkBlock(std::nullopt, true);
+  EndAndLinkBlock(std::nullopt, true, false);
 }
 
-void CPU::NewRec::AArch64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test)
+void CPU::NewRec::AArch64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test,
+                                                   bool force_run_events)
 {
   // event test
   // pc should've been flushed
-  DebugAssert(!m_dirty_pc);
+  DebugAssert(!m_dirty_pc && !m_block_ended);
+  m_block_ended = true;
 
   // TODO: try extracting this to a function
 
@@ -392,7 +394,11 @@ void CPU::NewRec::AArch64Compiler::EndAndLinkBlock(const std::optional<u32>& new
     armEmitCondBranch(armAsm, ge, CodeCache::g_run_events_and_dispatch);
 
   // jump to dispatcher or next block
-  if (!newpc.has_value())
+  if (force_run_events)
+  {
+    armEmitJmp(armAsm, CodeCache::g_run_events_and_dispatch, false);
+  }
+  else if (!newpc.has_value())
   {
     armEmitJmp(armAsm, CodeCache::g_dispatcher, false);
   }
@@ -410,8 +416,6 @@ void CPU::NewRec::AArch64Compiler::EndAndLinkBlock(const std::optional<u32>& new
       armEmitJmp(armAsm, target, true);
     }
   }
-
-  m_block_ended = true;
 }
 
 const void* CPU::NewRec::AArch64Compiler::EndCompile(u32* code_size, u32* far_code_size)
@@ -1953,9 +1957,29 @@ void CPU::NewRec::AArch64Compiler::TestInterrupts(const vixl::aarch64::WRegister
 
   SwitchToFarCode(true, ne);
   BackupHostState();
-  Flush(FLUSH_FLUSH_MIPS_REGISTERS | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
-  EmitCall(reinterpret_cast<const void*>(&DispatchInterrupt));
-  EndBlock(std::nullopt, true);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
+
+  // Can't use EndBlockWithException() here, because it'll use the wrong PC.
+  // Can't use RaiseException() on the fast path if we're the last instruction, because the next PC is unknown.
+  if (!iinfo->is_last_instruction)
+  {
+    EmitMov(RWARG1, Cop0Registers::CAUSE::MakeValueForException(Exception::INT, iinfo->is_branch_instruction, false,
+                                                                (inst + 1)->cop.cop_n));
+    EmitMov(RWARG2, m_compiler_pc);
+    EmitCall(reinterpret_cast<const void*>(static_cast<void (*)(u32, u32)>(&CPU::RaiseException)));
+    EndAndLinkBlock(std::nullopt, true, false);
+  }
+  else
+  {
+    if (m_dirty_pc)
+      EmitMov(RWARG1, m_compiler_pc);
+    armAsm->str(wzr, PTR(&g_state.downcount));
+    if (m_dirty_pc)
+      armAsm->str(RWARG1, PTR(&g_state.pc));
+    m_dirty_pc = false;
+    EndAndLinkBlock(std::nullopt, false, true);
+  }
+
   RestoreHostState();
   SwitchToNearCode(false);
 
diff --git a/src/core/cpu_newrec_compiler_aarch64.h b/src/core/cpu_newrec_compiler_aarch64.h
index 46a27ba64..c5749f890 100644
--- a/src/core/cpu_newrec_compiler_aarch64.h
+++ b/src/core/cpu_newrec_compiler_aarch64.h
@@ -34,7 +34,7 @@ protected:
   void GenerateCall(const void* func, s32 arg1reg = -1, s32 arg2reg = -1, s32 arg3reg = -1) override;
   void EndBlock(const std::optional<u32>& newpc, bool do_event_test) override;
   void EndBlockWithException(Exception excode) override;
-  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test);
+  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test, bool force_run_events);
   const void* EndCompile(u32* code_size, u32* far_code_size) override;
 
   void Flush(u32 flags) override;
diff --git a/src/core/cpu_newrec_compiler_riscv64.cpp b/src/core/cpu_newrec_compiler_riscv64.cpp
index e4700b241..6ad9c2e32 100644
--- a/src/core/cpu_newrec_compiler_riscv64.cpp
+++ b/src/core/cpu_newrec_compiler_riscv64.cpp
@@ -586,14 +586,14 @@ void CPU::NewRec::RISCV64Compiler::EndBlock(const std::optional<u32>& newpc, boo
 
   // flush regs
   Flush(FLUSH_END_BLOCK);
-  EndAndLinkBlock(newpc, do_event_test);
+  EndAndLinkBlock(newpc, do_event_test, false);
 }
 
 void CPU::NewRec::RISCV64Compiler::EndBlockWithException(Exception excode)
 {
   // flush regs, but not pc, it's going to get overwritten
   // flush cycles because of the GTE instruction stuff...
-  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
 
   // TODO: flush load delay
   // TODO: break for pcdrv
@@ -604,14 +604,16 @@ void CPU::NewRec::RISCV64Compiler::EndBlockWithException(Exception excode)
   EmitCall(reinterpret_cast<const void*>(static_cast<void (*)(u32, u32)>(&CPU::RaiseException)));
   m_dirty_pc = false;
 
-  EndAndLinkBlock(std::nullopt, true);
+  EndAndLinkBlock(std::nullopt, true, false);
 }
 
-void CPU::NewRec::RISCV64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test)
+void CPU::NewRec::RISCV64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test,
+                                                   bool force_run_events)
 {
   // event test
   // pc should've been flushed
-  DebugAssert(!m_dirty_pc);
+  DebugAssert(!m_dirty_pc && !m_block_ended);
+  m_block_ended = true;
 
   // TODO: try extracting this to a function
   // TODO: move the cycle flush in here..
@@ -646,7 +648,11 @@ void CPU::NewRec::RISCV64Compiler::EndAndLinkBlock(const std::optional<u32>& new
   }
 
   // jump to dispatcher or next block
-  if (!newpc.has_value())
+  if (force_run_events)
+  {
+    rvEmitJmp(rvAsm, CodeCache::g_run_events_and_dispatch);
+  }
+  else if (!newpc.has_value())
   {
     rvEmitJmp(rvAsm, CodeCache::g_dispatcher);
   }
@@ -664,8 +670,6 @@ void CPU::NewRec::RISCV64Compiler::EndAndLinkBlock(const std::optional<u32>& new
       rvEmitJmp(rvAsm, target);
     }
   }
-
-  m_block_ended = true;
 }
 
 const void* CPU::NewRec::RISCV64Compiler::EndCompile(u32* code_size, u32* far_code_size)
@@ -2209,9 +2213,29 @@ void CPU::NewRec::RISCV64Compiler::TestInterrupts(const biscuit::GPR& sr)
   rvAsm->ANDI(sr, sr, 0xFF);
   SwitchToFarCode(true, &Assembler::BEQ, sr, zero);
   BackupHostState();
-  Flush(FLUSH_FLUSH_MIPS_REGISTERS | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
-  EmitCall(reinterpret_cast<const void*>(&DispatchInterrupt));
-  EndBlock(std::nullopt, true);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
+
+  // Can't use EndBlockWithException() here, because it'll use the wrong PC.
+  // Can't use RaiseException() on the fast path if we're the last instruction, because the next PC is unknown.
+  if (!iinfo->is_last_instruction)
+  {
+    EmitMov(RARG1, Cop0Registers::CAUSE::MakeValueForException(Exception::INT, iinfo->is_branch_instruction, false,
+                                                               (inst + 1)->cop.cop_n));
+    EmitMov(RARG2, m_compiler_pc);
+    EmitCall(reinterpret_cast<const void*>(static_cast<void (*)(u32, u32)>(&CPU::RaiseException)));
+    EndAndLinkBlock(std::nullopt, true, false);
+  }
+  else
+  {
+    if (m_dirty_pc)
+      EmitMov(RARG1, m_compiler_pc);
+    rvAsm->SW(biscuit::zero, PTR(&g_state.downcount));
+    if (m_dirty_pc)
+      rvAsm->SW(RARG1, PTR(&g_state.pc));
+    m_dirty_pc = false;
+    EndAndLinkBlock(std::nullopt, false, true);
+  }
+
   RestoreHostState();
   SwitchToNearCode(false);
 
diff --git a/src/core/cpu_newrec_compiler_riscv64.h b/src/core/cpu_newrec_compiler_riscv64.h
index a91af4745..041e00dad 100644
--- a/src/core/cpu_newrec_compiler_riscv64.h
+++ b/src/core/cpu_newrec_compiler_riscv64.h
@@ -31,7 +31,7 @@ protected:
   void GenerateCall(const void* func, s32 arg1reg = -1, s32 arg2reg = -1, s32 arg3reg = -1) override;
   void EndBlock(const std::optional<u32>& newpc, bool do_event_test) override;
   void EndBlockWithException(Exception excode) override;
-  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test);
+  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test, bool force_run_events);
   const void* EndCompile(u32* code_size, u32* far_code_size) override;
 
   void Flush(u32 flags) override;
diff --git a/src/core/cpu_newrec_compiler_x64.cpp b/src/core/cpu_newrec_compiler_x64.cpp
index fde0d9b7a..ae57d6b09 100644
--- a/src/core/cpu_newrec_compiler_x64.cpp
+++ b/src/core/cpu_newrec_compiler_x64.cpp
@@ -222,14 +222,14 @@ void CPU::NewRec::X64Compiler::EndBlock(const std::optional<u32>& newpc, bool do
 
   // flush regs
   Flush(FLUSH_END_BLOCK);
-  EndAndLinkBlock(newpc, do_event_test);
+  EndAndLinkBlock(newpc, do_event_test, false);
 }
 
 void CPU::NewRec::X64Compiler::EndBlockWithException(Exception excode)
 {
   // flush regs, but not pc, it's going to get overwritten
   // flush cycles because of the GTE instruction stuff...
-  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
 
   // TODO: flush load delay
   // TODO: break for pcdrv
@@ -240,14 +240,16 @@ void CPU::NewRec::X64Compiler::EndBlockWithException(Exception excode)
   cg->call(static_cast<void (*)(u32, u32)>(&CPU::RaiseException));
   m_dirty_pc = false;
 
-  EndAndLinkBlock(std::nullopt, true);
+  EndAndLinkBlock(std::nullopt, true, false);
 }
 
-void CPU::NewRec::X64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test)
+void CPU::NewRec::X64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test,
+                                               bool force_run_events)
 {
   // event test
   // pc should've been flushed
-  DebugAssert(!m_dirty_pc);
+  DebugAssert(!m_dirty_pc && !m_block_ended);
+  m_block_ended = true;
 
   // TODO: try extracting this to a function
 
@@ -261,6 +263,12 @@ void CPU::NewRec::X64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc,
       cg->inc(cg->dword[PTR(&g_state.pending_ticks)]);
     else if (cycles > 0)
       cg->add(cg->dword[PTR(&g_state.pending_ticks)], cycles);
+
+    if (force_run_events)
+    {
+      cg->jmp(CodeCache::g_run_events_and_dispatch);
+      return;
+    }
   }
   else
   {
@@ -303,8 +311,6 @@ void CPU::NewRec::X64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc,
       cg->jmp(target, CodeGenerator::T_NEAR);
     }
   }
-
-  m_block_ended = true;
 }
 
 const void* CPU::NewRec::X64Compiler::EndCompile(u32* code_size, u32* far_code_size)
@@ -1339,7 +1345,7 @@ Xbyak::Reg32 CPU::NewRec::X64Compiler::GenerateLoad(const Xbyak::Reg32& addr_reg
     cg->mov(RWARG2, m_current_instruction_pc);
     cg->call(static_cast<void (*)(u32, u32)>(&CPU::RaiseException));
     m_dirty_pc = false;
-    EndAndLinkBlock(std::nullopt, true);
+    EndAndLinkBlock(std::nullopt, true, false);
 
     SwitchToNearCode(false);
     RestoreHostState();
@@ -1459,7 +1465,7 @@ void CPU::NewRec::X64Compiler::GenerateStore(const Xbyak::Reg32& addr_reg, const
     cg->mov(RWARG2, m_current_instruction_pc);
     cg->call(reinterpret_cast<const void*>(static_cast<void (*)(u32, u32)>(&CPU::RaiseException)));
     m_dirty_pc = false;
-    EndAndLinkBlock(std::nullopt, true);
+    EndAndLinkBlock(std::nullopt, true, false);
 
     SwitchToNearCode(false);
     RestoreHostState();
@@ -1929,9 +1935,28 @@ void CPU::NewRec::X64Compiler::TestInterrupts(const Xbyak::Reg32& sr)
 
   SwitchToFarCode(true, &CodeGenerator::jnz);
   BackupHostState();
-  Flush(FLUSH_FLUSH_MIPS_REGISTERS | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
-  cg->call(reinterpret_cast<const void*>(&DispatchInterrupt));
-  EndBlock(std::nullopt, true);
+  Flush(FLUSH_END_BLOCK | FLUSH_FOR_EXCEPTION | FLUSH_FOR_C_CALL);
+
+  // Can't use EndBlockWithException() here, because it'll use the wrong PC.
+  // Can't use RaiseException() on the fast path if we're the last instruction, because the next PC is unknown.
+  if (!iinfo->is_last_instruction)
+  {
+    cg->mov(RWARG1, Cop0Registers::CAUSE::MakeValueForException(Exception::INT, iinfo->is_branch_instruction, false,
+                                                                (inst + 1)->cop.cop_n));
+    cg->mov(RWARG2, m_compiler_pc);
+    cg->call(static_cast<void (*)(u32, u32)>(&CPU::RaiseException));
+    m_dirty_pc = false;
+    EndAndLinkBlock(std::nullopt, true, false);
+  }
+  else
+  {
+    if (m_dirty_pc)
+      cg->mov(cg->dword[PTR(&g_state.pc)], m_compiler_pc);
+    m_dirty_pc = false;
+    cg->mov(cg->dword[PTR(&g_state.downcount)], 0);
+    EndAndLinkBlock(std::nullopt, false, true);
+  }
+
   RestoreHostState();
   SwitchToNearCode(false);
 
diff --git a/src/core/cpu_newrec_compiler_x64.h b/src/core/cpu_newrec_compiler_x64.h
index e99ba1993..1c90eb93b 100644
--- a/src/core/cpu_newrec_compiler_x64.h
+++ b/src/core/cpu_newrec_compiler_x64.h
@@ -33,7 +33,7 @@ protected:
   void GenerateCall(const void* func, s32 arg1reg = -1, s32 arg2reg = -1, s32 arg3reg = -1) override;
   void EndBlock(const std::optional<u32>& newpc, bool do_event_test) override;
   void EndBlockWithException(Exception excode) override;
-  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test);
+  void EndAndLinkBlock(const std::optional<u32>& newpc, bool do_event_test, bool force_run_events);
   const void* EndCompile(u32* code_size, u32* far_code_size) override;
 
   void Flush(u32 flags) override;
diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp
index ef7438176..b7b98843f 100644
--- a/src/core/cpu_recompiler_code_generator.cpp
+++ b/src/core/cpu_recompiler_code_generator.cpp
@@ -2740,7 +2740,14 @@ bool CodeGenerator::Compile_cop0(Instruction instruction, const CodeCache::Instr
             EmitAnd(sr_value.host_reg, sr_value.host_reg, cause_value);
             EmitTest(sr_value.host_reg, Value::FromConstantU32(0xFF00));
             EmitConditionalBranch(Condition::Zero, false, &no_interrupt);
+
+            EmitBranch(GetCurrentFarCodePointer());
+            SwitchToFarCode();
+            WriteNewPC(CalculatePC(), false);
             EmitStoreCPUStructField(offsetof(State, downcount), Value::FromConstantU32(0));
+            EmitExceptionExit();
+            SwitchToNearCode();
+
             EmitBindLabel(&no_interrupt);
             m_register_cache.UninhibitAllocation();
           }