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& 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(static_cast(&CPU::RaiseException))); m_dirty_pc = false; - EndAndLinkBlock(std::nullopt, true); + EndAndLinkBlock(std::nullopt, true, false); } -void CPU::NewRec::AArch32Compiler::EndAndLinkBlock(const std::optional& newpc, bool do_event_test) +void CPU::NewRec::AArch32Compiler::EndAndLinkBlock(const std::optional& 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& 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& 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(&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(static_cast(&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& newpc, bool do_event_test) override; void EndBlockWithException(Exception excode) override; - void EndAndLinkBlock(const std::optional& newpc, bool do_event_test); + void EndAndLinkBlock(const std::optional& 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& 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(static_cast(&CPU::RaiseException))); m_dirty_pc = false; - EndAndLinkBlock(std::nullopt, true); + EndAndLinkBlock(std::nullopt, true, false); } -void CPU::NewRec::AArch64Compiler::EndAndLinkBlock(const std::optional& newpc, bool do_event_test) +void CPU::NewRec::AArch64Compiler::EndAndLinkBlock(const std::optional& 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& 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& 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(&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(static_cast(&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& newpc, bool do_event_test) override; void EndBlockWithException(Exception excode) override; - void EndAndLinkBlock(const std::optional& newpc, bool do_event_test); + void EndAndLinkBlock(const std::optional& 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& 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(static_cast(&CPU::RaiseException))); m_dirty_pc = false; - EndAndLinkBlock(std::nullopt, true); + EndAndLinkBlock(std::nullopt, true, false); } -void CPU::NewRec::RISCV64Compiler::EndAndLinkBlock(const std::optional& newpc, bool do_event_test) +void CPU::NewRec::RISCV64Compiler::EndAndLinkBlock(const std::optional& 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& 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& 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(&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(static_cast(&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& newpc, bool do_event_test) override; void EndBlockWithException(Exception excode) override; - void EndAndLinkBlock(const std::optional& newpc, bool do_event_test); + void EndAndLinkBlock(const std::optional& 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& 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(&CPU::RaiseException)); m_dirty_pc = false; - EndAndLinkBlock(std::nullopt, true); + EndAndLinkBlock(std::nullopt, true, false); } -void CPU::NewRec::X64Compiler::EndAndLinkBlock(const std::optional& newpc, bool do_event_test) +void CPU::NewRec::X64Compiler::EndAndLinkBlock(const std::optional& 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& 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& 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(&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(static_cast(&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(&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(&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& newpc, bool do_event_test) override; void EndBlockWithException(Exception excode) override; - void EndAndLinkBlock(const std::optional& newpc, bool do_event_test); + void EndAndLinkBlock(const std::optional& 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(); }