diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index ba580b11b..9bf0f2fd1 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -23,7 +23,6 @@ static void SetPC(u32 new_pc); static void UpdateLoadDelay(); static void Branch(u32 target); static void FlushPipeline(); -static void UpdateDebugDispatcherFlag(); State g_state; bool g_using_interpreter = false; @@ -227,34 +226,13 @@ ALWAYS_INLINE_RELEASE void Branch(u32 target) g_state.branch_was_taken = true; } -ALWAYS_INLINE static u32 GetExceptionVector(Exception excode) +ALWAYS_INLINE static u32 GetExceptionVector(bool debug_exception = false) { const u32 base = g_state.cop0_regs.sr.BEV ? UINT32_C(0xbfc00100) : UINT32_C(0x80000000); - -#if 0 - // apparently this isn't correct... - switch (excode) - { - case Exception::BP: - return base | UINT32_C(0x00000040); - - default: - return base | UINT32_C(0x00000080); - } -#else - return base | UINT32_C(0x00000080); -#endif + return base | (debug_exception ? UINT32_C(0x00000040) : UINT32_C(0x00000080)); } -void RaiseException(Exception excode) -{ - RaiseException(Cop0Registers::CAUSE::MakeValueForException(excode, g_state.current_instruction_in_branch_delay_slot, - g_state.current_instruction_was_branch_taken, - g_state.current_instruction.cop.cop_n), - g_state.current_instruction_pc); -} - -void RaiseException(u32 CAUSE_bits, u32 EPC) +ALWAYS_INLINE_RELEASE static void RaiseException(u32 CAUSE_bits, u32 EPC, u32 vector) { g_state.cop0_regs.EPC = EPC; g_state.cop0_regs.cause.bits = (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::EXCEPTION_WRITE_MASK) | @@ -264,10 +242,10 @@ void RaiseException(u32 CAUSE_bits, u32 EPC) if (g_state.cop0_regs.cause.Excode != Exception::INT && g_state.cop0_regs.cause.Excode != Exception::Syscall && g_state.cop0_regs.cause.Excode != Exception::BP) { - Log_DebugPrintf("Exception %u at 0x%08X (epc=0x%08X, BD=%s, CE=%u)", - static_cast(g_state.cop0_regs.cause.Excode.GetValue()), g_state.current_instruction_pc, - g_state.cop0_regs.EPC, g_state.cop0_regs.cause.BD ? "true" : "false", - g_state.cop0_regs.cause.CE.GetValue()); + Log_DevPrintf("Exception %u at 0x%08X (epc=0x%08X, BD=%s, CE=%u)", + static_cast(g_state.cop0_regs.cause.Excode.GetValue()), g_state.current_instruction_pc, + g_state.cop0_regs.EPC, g_state.cop0_regs.cause.BD ? "true" : "false", + g_state.cop0_regs.cause.CE.GetValue()); DisassembleAndPrint(g_state.current_instruction_pc, 4, 0); if (s_trace_to_log) { @@ -291,11 +269,36 @@ void RaiseException(u32 CAUSE_bits, u32 EPC) g_state.cop0_regs.sr.mode_bits <<= 2; // flush the pipeline - we don't want to execute the previously fetched instruction - g_state.regs.npc = GetExceptionVector(g_state.cop0_regs.cause.Excode); + g_state.regs.npc = vector; g_state.exception_raised = true; FlushPipeline(); } +ALWAYS_INLINE_RELEASE static void DispatchCop0Breakpoint() +{ + // When a breakpoint address match occurs the PSX jumps to 80000040h (ie. unlike normal exceptions, not to 80000080h). + // The Excode value in the CAUSE register is set to 09h (same as BREAK opcode), and EPC contains the return address, + // as usually. One of the first things to be done in the exception handler is to disable breakpoints (eg. if the + // any-jump break is enabled, then it must be disabled BEFORE jumping from 80000040h to the actual exception handler). + RaiseException(Cop0Registers::CAUSE::MakeValueForException( + Exception::BP, g_state.current_instruction_in_branch_delay_slot, + g_state.current_instruction_was_branch_taken, g_state.current_instruction.cop.cop_n), + g_state.current_instruction_pc, GetExceptionVector(true)); +} + +void RaiseException(u32 CAUSE_bits, u32 EPC) +{ + RaiseException(CAUSE_bits, EPC, GetExceptionVector()); +} + +void RaiseException(Exception excode) +{ + RaiseException(Cop0Registers::CAUSE::MakeValueForException(excode, g_state.current_instruction_in_branch_delay_slot, + g_state.current_instruction_was_branch_taken, + g_state.current_instruction.cop.cop_n), + g_state.current_instruction_pc, GetExceptionVector()); +} + void SetExternalInterrupt(u8 bit) { g_state.cop0_regs.cause.Ip |= static_cast(1u << bit); @@ -466,6 +469,7 @@ ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value) g_state.cop0_regs.dcic.bits = (g_state.cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK); Log_WarningPrintf("COP0 DCIC <- %08X (now %08X)", value, g_state.cop0_regs.dcic.bits); + UpdateDebugDispatcherFlag(); } break; @@ -488,12 +492,83 @@ ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value) break; default: - Log_DevPrintf("Unknown COP0 reg %u", ZeroExtend32(static_cast(reg))); + Log_WarningPrintf("Unknown COP0 reg write %u (%08X)", ZeroExtend32(static_cast(reg)), value); break; } } -static void PrintInstruction(u32 bits, u32 pc, Registers* regs) +ALWAYS_INLINE_RELEASE void Cop0ExecutionBreakpointCheck() +{ + if (!g_state.cop0_regs.dcic.ExecutionBreakpointsEnabled()) + return; + + const u32 pc = g_state.current_instruction_pc; + const u32 bpc = g_state.cop0_regs.BPC; + const u32 bpcm = g_state.cop0_regs.BPCM; + + // Break condition is "((PC XOR BPC) AND BPCM)=0". + if (bpcm == 0 || ((pc ^ bpc) & bpcm) != 0u) + return; + + Log_DevPrintf("Cop0 execution breakpoint at %08X", pc); + g_state.cop0_regs.dcic.status_any_break = true; + g_state.cop0_regs.dcic.status_bpc_code_break = true; + DispatchCop0Breakpoint(); +} + +template +ALWAYS_INLINE_RELEASE void Cop0DataBreakpointCheck(VirtualMemoryAddress address) +{ + if constexpr (type == MemoryAccessType::Read) + { + if (!g_state.cop0_regs.dcic.DataReadBreakpointsEnabled()) + return; + } + else + { + if (!g_state.cop0_regs.dcic.DataWriteBreakpointsEnabled()) + return; + } + + // Break condition is "((addr XOR BDA) AND BDAM)=0". + const u32 bda = g_state.cop0_regs.BDA; + const u32 bdam = g_state.cop0_regs.BDAM; + if (bdam == 0 || ((address ^ bda) & bdam) != 0u) + return; + + Log_DevPrintf("Cop0 data breakpoint for %08X at %08X", address, g_state.current_instruction_pc); + + g_state.cop0_regs.dcic.status_any_break = true; + g_state.cop0_regs.dcic.status_bda_data_break = true; + if constexpr (type == MemoryAccessType::Read) + g_state.cop0_regs.dcic.status_bda_data_read_break = true; + else + g_state.cop0_regs.dcic.status_bda_data_write_break = true; + + DispatchCop0Breakpoint(); +} + +static void TracePrintInstruction() +{ + const u32 pc = g_state.current_instruction_pc; + const u32 bits = g_state.current_instruction.bits; + + TinyString instr; + TinyString comment; + DisassembleInstruction(&instr, pc, bits); + DisassembleInstructionComment(&comment, pc, bits, &g_state.regs); + if (!comment.IsEmpty()) + { + for (u32 i = instr.GetLength(); i < 30; i++) + instr.AppendCharacter(' '); + instr.AppendString("; "); + instr.AppendString(comment); + } + + std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); +} + +static void PrintInstruction(u32 bits, u32 pc, Registers* regs, const char* prefix) { TinyString instr; TinyString comment; @@ -507,7 +582,7 @@ static void PrintInstruction(u32 bits, u32 pc, Registers* regs) instr.AppendString(comment); } - std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); + Log_DevPrintf("%s%08x: %08x %s", prefix, pc, bits, instr.GetCharArray()); } static void LogInstruction(u32 bits, u32 pc, Registers* regs) @@ -537,11 +612,11 @@ ALWAYS_INLINE static constexpr bool SubOverflow(u32 old_value, u32 sub_value, u3 return (((new_value ^ old_value) & (old_value ^ sub_value)) & UINT32_C(0x80000000)) != 0; } -void DisassembleAndPrint(u32 addr) +void DisassembleAndPrint(u32 addr, const char* prefix) { u32 bits = 0; SafeReadMemoryWord(addr, &bits); - PrintInstruction(bits, addr, &g_state.regs); + PrintInstruction(bits, addr, &g_state.regs, prefix); } void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instructions_after /* = 0 */) @@ -549,21 +624,19 @@ void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instru u32 disasm_addr = addr - (instructions_before * sizeof(u32)); for (u32 i = 0; i < instructions_before; i++) { - DisassembleAndPrint(disasm_addr); + DisassembleAndPrint(disasm_addr, ""); disasm_addr += sizeof(u32); } - std::printf("----> "); - // <= to include the instruction itself for (u32 i = 0; i <= instructions_after; i++) { - DisassembleAndPrint(disasm_addr); + DisassembleAndPrint(disasm_addr, (i == 0) ? "---->" : ""); disasm_addr += sizeof(u32); } } -template +template ALWAYS_INLINE_RELEASE static void ExecuteInstruction() { restart_instruction: @@ -579,7 +652,7 @@ restart_instruction: #ifdef _DEBUG if (TRACE_EXECUTION) - PrintInstruction(inst.bits, g_state.current_instruction_pc, &g_state.regs); + TracePrintInstruction(); #endif // Skip nops. Makes PGXP-CPU quicker, but also the regular interpreter. @@ -1059,6 +1132,9 @@ restart_instruction: case InstructionOp::lb: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + u8 value; if (!ReadMemoryByte(addr, &value)) return; @@ -1075,6 +1151,9 @@ restart_instruction: case InstructionOp::lh: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + u16 value; if (!ReadMemoryHalfWord(addr, &value)) return; @@ -1090,6 +1169,9 @@ restart_instruction: case InstructionOp::lw: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + u32 value; if (!ReadMemoryWord(addr, &value)) return; @@ -1104,6 +1186,9 @@ restart_instruction: case InstructionOp::lbu: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + u8 value; if (!ReadMemoryByte(addr, &value)) return; @@ -1119,6 +1204,9 @@ restart_instruction: case InstructionOp::lhu: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + u16 value; if (!ReadMemoryHalfWord(addr, &value)) return; @@ -1136,6 +1224,9 @@ restart_instruction: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + u32 aligned_value; if (!ReadMemoryWord(aligned_addr, &aligned_value)) return; @@ -1165,6 +1256,9 @@ restart_instruction: case InstructionOp::sb: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + const u8 value = Truncate8(ReadReg(inst.i.rt)); WriteMemoryByte(addr, value); @@ -1176,6 +1270,9 @@ restart_instruction: case InstructionOp::sh: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + const u16 value = Truncate16(ReadReg(inst.i.rt)); WriteMemoryHalfWord(addr, value); @@ -1187,6 +1284,9 @@ restart_instruction: case InstructionOp::sw: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + if constexpr (debug) + Cop0DataBreakpointCheck(addr); + const u32 value = ReadReg(inst.i.rt); WriteMemoryWord(addr, value); @@ -1200,6 +1300,9 @@ restart_instruction: { const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3); + if constexpr (debug) + Cop0DataBreakpointCheck(aligned_addr); + const u32 reg_value = ReadReg(inst.i.rt); const u8 shift = (Truncate8(addr) & u8(3)) * u8(8); u32 mem_value; @@ -1508,16 +1611,20 @@ void DispatchInterrupt() g_state.regs.pc); } -static void UpdateDebugDispatcherFlag() +void UpdateDebugDispatcherFlag() { const bool has_any_breakpoints = !s_breakpoints.empty(); // TODO: cop0 breakpoints + const auto& dcic = g_state.cop0_regs.dcic; + const bool has_cop0_breakpoints = + dcic.super_master_enable_1 && dcic.super_master_enable_2 && dcic.execution_breakpoint_enable; - const bool use_debug_dispatcher = has_any_breakpoints || s_trace_to_log; + const bool use_debug_dispatcher = has_any_breakpoints || has_cop0_breakpoints || s_trace_to_log; if (use_debug_dispatcher == g_state.use_debug_dispatcher) return; + Log_DevPrintf("%s debug dispatcher", use_debug_dispatcher ? "Now using" : "No longer using"); g_state.use_debug_dispatcher = use_debug_dispatcher; ForceDispatcherExit(); } @@ -1694,7 +1801,7 @@ bool AddStepOutBreakpoint(u32 max_instructions_to_search) return false; } -static ALWAYS_INLINE_RELEASE bool BreakpointCheck() +ALWAYS_INLINE_RELEASE static bool BreakpointCheck() { const u32 pc = g_state.regs.pc; @@ -1779,6 +1886,8 @@ static void ExecuteImpl() if constexpr (debug) { + Cop0ExecutionBreakpointCheck(); + if (BreakpointCheck()) { // continue is measurably faster than break on msvc for some reason @@ -1818,7 +1927,7 @@ static void ExecuteImpl() #endif // execute the instruction we previously fetched - ExecuteInstruction(); + ExecuteInstruction(); // next load delay UpdateLoadDelay(); @@ -1891,7 +2000,7 @@ void InterpretCachedBlock(const CodeBlock& block) g_state.regs.npc += 4; // execute the instruction we previously fetched - ExecuteInstruction(); + ExecuteInstruction(); // next load delay UpdateLoadDelay(); @@ -1944,7 +2053,7 @@ void InterpretUncachedBlock() } // execute the instruction we previously fetched - ExecuteInstruction(); + ExecuteInstruction(); // next load delay UpdateLoadDelay(); @@ -1969,13 +2078,13 @@ namespace Recompiler::Thunks { bool InterpretInstruction() { - ExecuteInstruction(); + ExecuteInstruction(); return g_state.exception_raised; } bool InterpretInstructionPGXP() { - ExecuteInstruction(); + ExecuteInstruction(); return g_state.exception_raised; } diff --git a/src/core/cpu_core_private.h b/src/core/cpu_core_private.h index a6182641d..9a8a442bf 100644 --- a/src/core/cpu_core_private.h +++ b/src/core/cpu_core_private.h @@ -21,6 +21,7 @@ ALWAYS_INLINE void CheckForPendingInterrupt() } void DispatchInterrupt(); +void UpdateDebugDispatcherFlag(); // icache stuff ALWAYS_INLINE bool IsCachedAddress(VirtualMemoryAddress address) diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index 534e21099..45f3a52db 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -2494,25 +2494,55 @@ bool CodeGenerator::Compile_cop0(const CodeBlockInstruction& cbi) } } - if (cbi.instruction.cop.CommonOp() == CopCommonInstruction::mtcn && - (reg == Cop0Reg::CAUSE || reg == Cop0Reg::SR)) + if (cbi.instruction.cop.CommonOp() == CopCommonInstruction::mtcn) { - // Emit an interrupt check on load of CAUSE/SR. - Value sr_value = m_register_cache.AllocateScratch(RegSize_32); - Value cause_value = m_register_cache.AllocateScratch(RegSize_32); + if (reg == Cop0Reg::CAUSE || reg == Cop0Reg::SR) + { + // Emit an interrupt check on load of CAUSE/SR. + Value sr_value = m_register_cache.AllocateScratch(RegSize_32); + Value cause_value = m_register_cache.AllocateScratch(RegSize_32); - // m_cop0_regs.sr.IEc && ((m_cop0_regs.cause.Ip & m_cop0_regs.sr.Im) != 0) - LabelType no_interrupt; - EmitLoadCPUStructField(sr_value.host_reg, sr_value.size, offsetof(State, cop0_regs.sr.bits)); - EmitLoadCPUStructField(cause_value.host_reg, cause_value.size, offsetof(State, cop0_regs.cause.bits)); - EmitBranchIfBitClear(sr_value.host_reg, sr_value.size, 0, &no_interrupt); - m_register_cache.InhibitAllocation(); - 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); - EmitStoreCPUStructField(offsetof(State, downcount), Value::FromConstantU32(0)); - EmitBindLabel(&no_interrupt); - m_register_cache.UninhibitAllocation(); + // m_cop0_regs.sr.IEc && ((m_cop0_regs.cause.Ip & m_cop0_regs.sr.Im) != 0) + LabelType no_interrupt; + EmitLoadCPUStructField(sr_value.host_reg, sr_value.size, offsetof(State, cop0_regs.sr.bits)); + EmitLoadCPUStructField(cause_value.host_reg, cause_value.size, offsetof(State, cop0_regs.cause.bits)); + EmitBranchIfBitClear(sr_value.host_reg, sr_value.size, 0, &no_interrupt); + m_register_cache.InhibitAllocation(); + 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); + EmitStoreCPUStructField(offsetof(State, downcount), Value::FromConstantU32(0)); + EmitBindLabel(&no_interrupt); + m_register_cache.UninhibitAllocation(); + } + else if (reg == Cop0Reg::DCIC) + { + Value dcic_value = m_register_cache.AllocateScratch(RegSize_32); + m_register_cache.InhibitAllocation(); + + // if ((dcic & master_enable_bits) != master_enable_bits) goto not_enabled; + LabelType not_enabled; + EmitLoadCPUStructField(dcic_value.host_reg, dcic_value.size, offsetof(State, cop0_regs.dcic.bits)); + EmitAnd(dcic_value.host_reg, dcic_value.host_reg, + Value::FromConstantU32(Cop0Registers::DCIC::MASTER_ENABLE_BITS)); + EmitConditionalBranch(Condition::NotEqual, false, dcic_value.host_reg, + Value::FromConstantU32(Cop0Registers::DCIC::MASTER_ENABLE_BITS), ¬_enabled); + + // if ((dcic & breakpoint_bits) == 0) goto not_enabled; + EmitLoadCPUStructField(dcic_value.host_reg, dcic_value.size, offsetof(State, cop0_regs.dcic.bits)); + EmitTest(dcic_value.host_reg, Value::FromConstantU32(Cop0Registers::DCIC::ANY_BREAKPOINTS_ENABLED_BITS)); + EmitConditionalBranch(Condition::Zero, false, ¬_enabled); + + m_register_cache.UninhibitAllocation(); + + // exit block early if enabled + m_register_cache.PushState(); + EmitFunctionCall(nullptr, &CPU::UpdateDebugDispatcherFlag); + EmitExceptionExit(); + m_register_cache.PopState(); + + EmitBindLabel(¬_enabled); + } } InstructionEpilogue(cbi); diff --git a/src/core/cpu_types.h b/src/core/cpu_types.h index 9fbb0b12c..1f805c49a 100644 --- a/src/core/cpu_types.h +++ b/src/core/cpu_types.h @@ -283,8 +283,7 @@ struct Registers }; }; -std::optional GetLoadStoreEffectiveAddress(const Instruction& instruction, - const Registers* regs); +std::optional GetLoadStoreEffectiveAddress(const Instruction& instruction, const Registers* regs); enum class Cop0Reg : u8 { @@ -402,6 +401,27 @@ struct Cop0Registers BitField super_master_enable_2; static constexpr u32 WRITE_MASK = 0b1111'1111'1000'0000'1111'0000'0011'1111; + + static constexpr u32 ANY_BREAKPOINTS_ENABLED_BITS = (1u << 24) | (1u << 26) | (1u << 27) | (1u << 28); + static constexpr u32 MASTER_ENABLE_BITS = (1u << 23) | (1u << 31); + + constexpr bool ExecutionBreakpointsEnabled() const + { + const u32 mask = (1u << 23) | (1u << 24) | (1u << 31); + return ((bits & mask) == mask); + } + + constexpr bool DataReadBreakpointsEnabled() const + { + const u32 mask = (1u << 23) | (1u << 25) | (1u << 26) | (1u << 31); + return ((bits & mask) == mask); + } + + constexpr bool DataWriteBreakpointsEnabled() const + { + const u32 mask = (1u << 23) | (1u << 25) | (1u << 27) | (1u << 31); + return ((bits & mask) == mask); + } } dcic; };