diff --git a/src/core/bus.cpp b/src/core/bus.cpp index 886f78da2..e6fa98296 100644 --- a/src/core/bus.cpp +++ b/src/core/bus.cpp @@ -811,6 +811,9 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& case 0x03: // KUSEG 1536M-2048M { // Above 512mb raises an exception. + if constexpr (type == MemoryAccessType::Read) + value = UINT32_C(0xFFFFFFFF); + return -1; } @@ -834,6 +837,9 @@ static ALWAYS_INLINE TickCount DoMemoryAccess(VirtualMemoryAddress address, u32& } else { + if constexpr (type == MemoryAccessType::Read) + value = UINT32_C(0xFFFFFFFF); + return -1; } } @@ -1106,7 +1112,7 @@ namespace Recompiler::Thunks { u64 ReadMemoryByte(u32 address) { - u32 temp = 0; + u32 temp; const TickCount cycles = DoMemoryAccess(address, temp); if (cycles < 0) return static_cast(-static_cast(Exception::DBE)); @@ -1123,7 +1129,7 @@ u64 ReadMemoryHalfWord(u32 address) return static_cast(-static_cast(Exception::AdEL)); } - u32 temp = 0; + u32 temp; const TickCount cycles = DoMemoryAccess(address, temp); if (cycles < 0) return static_cast(-static_cast(Exception::DBE)); @@ -1140,7 +1146,7 @@ u64 ReadMemoryWord(u32 address) return static_cast(-static_cast(Exception::AdEL)); } - u32 temp = 0; + u32 temp; const TickCount cycles = DoMemoryAccess(address, temp); if (cycles < 0) return static_cast(-static_cast(Exception::DBE)); @@ -1193,6 +1199,44 @@ u32 WriteMemoryWord(u32 address, u32 value) return 0; } +u32 UncheckedReadMemoryByte(u32 address) +{ + u32 temp; + g_state.pending_ticks += DoMemoryAccess(address, temp); + return temp; +} + +u32 UncheckedReadMemoryHalfWord(u32 address) +{ + u32 temp; + g_state.pending_ticks += DoMemoryAccess(address, temp); + return temp; +} + +u32 UncheckedReadMemoryWord(u32 address) +{ + u32 temp; + g_state.pending_ticks += DoMemoryAccess(address, temp); + return temp; +} + +void UncheckedWriteMemoryByte(u32 address, u8 value) +{ + u32 temp = ZeroExtend32(value); + g_state.pending_ticks += DoMemoryAccess(address, temp); +} + +void UncheckedWriteMemoryHalfWord(u32 address, u16 value) +{ + u32 temp = ZeroExtend32(value); + g_state.pending_ticks += DoMemoryAccess(address, temp); +} + +void UncheckedWriteMemoryWord(u32 address, u32 value) +{ + g_state.pending_ticks += DoMemoryAccess(address, value); +} + } // namespace Recompiler::Thunks } // namespace CPU diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index 4ef26ed50..48b02b70e 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -3,6 +3,7 @@ #include "common/assert.h" #include "common/log.h" #include "cpu_core.h" +#include "cpu_core_private.h" #include "cpu_disasm.h" #include "system.h" #include "timing_event.h" diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 9e06c3cb9..41edba0af 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -240,33 +240,6 @@ void ClearExternalInterrupt(u8 bit) g_state.cop0_regs.cause.Ip &= static_cast(~(1u << bit)); } -bool HasPendingInterrupt() -{ - // const bool do_interrupt = g_state.m_cop0_regs.sr.IEc && ((g_state.m_cop0_regs.cause.Ip & g_state.m_cop0_regs.sr.Im) - // != 0); - const bool do_interrupt = g_state.cop0_regs.sr.IEc && - (((g_state.cop0_regs.cause.bits & g_state.cop0_regs.sr.bits) & (UINT32_C(0xFF) << 8)) != 0); - - const bool interrupt_delay = g_state.interrupt_delay; - g_state.interrupt_delay = false; - - return do_interrupt && !interrupt_delay; -} - -void DispatchInterrupt() -{ - // If the instruction we're about to execute is a GTE instruction, delay dispatching the interrupt until the next - // instruction. For some reason, if we don't do this, we end up with incorrectly sorted polygons and flickering.. - if (g_state.next_instruction.IsCop2Instruction()) - return; - - // Interrupt raising occurs before the start of the instruction. - RaiseException( - Cop0Registers::CAUSE::MakeValueForException(Exception::INT, g_state.next_instruction_is_branch_delay_slot, - g_state.branch_was_taken, g_state.next_instruction.cop.cop_n), - g_state.regs.pc); -} - void UpdateLoadDelay() { // the old value is needed in case the delay slot instruction overwrites the same register diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index 148afdcfa..660596353 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -89,8 +89,6 @@ bool SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value); // External IRQs void SetExternalInterrupt(u8 bit); void ClearExternalInterrupt(u8 bit); -bool HasPendingInterrupt(); -void DispatchInterrupt(); void DisassembleAndPrint(u32 addr); void DisassembleAndLog(u32 addr); diff --git a/src/core/cpu_core_private.h b/src/core/cpu_core_private.h index 2887a181f..41ad24ec5 100644 --- a/src/core/cpu_core_private.h +++ b/src/core/cpu_core_private.h @@ -7,6 +7,33 @@ namespace CPU { void RaiseException(Exception excode); void RaiseException(u32 CAUSE_bits, u32 EPC); +ALWAYS_INLINE static bool HasPendingInterrupt() +{ + // const bool do_interrupt = g_state.m_cop0_regs.sr.IEc && ((g_state.m_cop0_regs.cause.Ip & g_state.m_cop0_regs.sr.Im) + // != 0); + const bool do_interrupt = g_state.cop0_regs.sr.IEc && + (((g_state.cop0_regs.cause.bits & g_state.cop0_regs.sr.bits) & (UINT32_C(0xFF) << 8)) != 0); + + const bool interrupt_delay = g_state.interrupt_delay; + g_state.interrupt_delay = false; + + return do_interrupt && !interrupt_delay; +} + +ALWAYS_INLINE static void DispatchInterrupt() +{ + // If the instruction we're about to execute is a GTE instruction, delay dispatching the interrupt until the next + // instruction. For some reason, if we don't do this, we end up with incorrectly sorted polygons and flickering.. + if (g_state.next_instruction.IsCop2Instruction()) + return; + + // Interrupt raising occurs before the start of the instruction. + RaiseException( + Cop0Registers::CAUSE::MakeValueForException(Exception::INT, g_state.next_instruction_is_branch_delay_slot, + g_state.branch_was_taken, g_state.next_instruction.cop.cop_n), + g_state.regs.pc); +} + // defined in cpu_memory.cpp - memory access functions which return false if an exception was thrown. bool FetchInstruction(); bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value); diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index bf161fffe..f2ee702f3 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -1626,7 +1626,8 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi) // we don't need to test the address of constant branches unless they're definitely misaligned, which would be // strange. - if (!branch_target.IsConstant() || (branch_target.constant_value & 0x3) != 0) + if (g_settings.cpu_recompiler_memory_exceptions && + (!branch_target.IsConstant() || (branch_target.constant_value & 0x3) != 0)) { LabelType branch_okay; diff --git a/src/core/cpu_recompiler_code_generator_aarch64.cpp b/src/core/cpu_recompiler_code_generator_aarch64.cpp index 8dd93e73c..693d17e81 100644 --- a/src/core/cpu_recompiler_code_generator_aarch64.cpp +++ b/src/core/cpu_recompiler_code_generator_aarch64.cpp @@ -5,6 +5,7 @@ #include "cpu_core_private.h" #include "cpu_recompiler_code_generator.h" #include "cpu_recompiler_thunks.h" +#include "settings.h" Log_SetChannel(CPU::Recompiler); namespace a64 = vixl::aarch64; @@ -1268,121 +1269,190 @@ Value CodeGenerator::EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const { AddPendingCycles(true); - // We need to use the full 64 bits here since we test the sign bit result. - Value result = m_register_cache.AllocateScratch(RegSize_64); - - // NOTE: This can leave junk in the upper bits - switch (size) + if (g_settings.cpu_recompiler_memory_exceptions) { - case RegSize_8: - EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address); - break; + // We need to use the full 64 bits here since we test the sign bit result. + Value result = m_register_cache.AllocateScratch(RegSize_64); - case RegSize_16: - EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address); - break; + // NOTE: This can leave junk in the upper bits + switch (size) + { + case RegSize_8: + EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address); + break; - case RegSize_32: - EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address); - break; + case RegSize_16: + EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address); + break; - default: - UnreachableCode(); - break; + case RegSize_32: + EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address); + break; + + default: + UnreachableCode(); + break; + } + + m_register_cache.PushState(); + + a64::Label load_okay; + m_emit->Tbz(GetHostReg64(result.host_reg), 63, &load_okay); + EmitBranch(GetCurrentFarCodePointer()); + m_emit->Bind(&load_okay); + + // load exception path + SwitchToFarCode(); + + // cause_bits = (-result << 2) | BD | cop_n + m_emit->neg(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg)); + m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2); + EmitOr(result.host_reg, result.host_reg, + Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException( + static_cast(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n))); + EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); + + EmitExceptionExit(); + SwitchToNearCode(); + + m_register_cache.PopState(); + + // Downcast to ignore upper 56/48/32 bits. This should be a noop. + switch (size) + { + case RegSize_8: + ConvertValueSizeInPlace(&result, RegSize_8, false); + break; + + case RegSize_16: + ConvertValueSizeInPlace(&result, RegSize_16, false); + break; + + case RegSize_32: + ConvertValueSizeInPlace(&result, RegSize_32, false); + break; + + default: + UnreachableCode(); + break; + } + + return result; } - - m_register_cache.PushState(); - - a64::Label load_okay; - m_emit->Tbz(GetHostReg64(result.host_reg), 63, &load_okay); - EmitBranch(GetCurrentFarCodePointer()); - m_emit->Bind(&load_okay); - - // load exception path - SwitchToFarCode(); - - // cause_bits = (-result << 2) | BD | cop_n - m_emit->neg(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg)); - m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2); - EmitOr(result.host_reg, result.host_reg, - Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException( - static_cast(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n))); - EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); - - EmitExceptionExit(); - SwitchToNearCode(); - - m_register_cache.PopState(); - - // Downcast to ignore upper 56/48/32 bits. This should be a noop. - switch (size) + else { - case RegSize_8: - ConvertValueSizeInPlace(&result, RegSize_8, false); - break; + Value result = m_register_cache.AllocateScratch(RegSize_32); + switch (size) + { + case RegSize_8: + EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryByte, address); + break; - case RegSize_16: - ConvertValueSizeInPlace(&result, RegSize_16, false); - break; + case RegSize_16: + EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryHalfWord, address); + break; - case RegSize_32: - ConvertValueSizeInPlace(&result, RegSize_32, false); - break; + case RegSize_32: + EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryWord, address); + break; - default: - UnreachableCode(); - break; + default: + UnreachableCode(); + break; + } + + // Downcast to ignore upper 56/48/32 bits. This should be a noop. + switch (size) + { + case RegSize_8: + ConvertValueSizeInPlace(&result, RegSize_8, false); + break; + + case RegSize_16: + ConvertValueSizeInPlace(&result, RegSize_16, false); + break; + + case RegSize_32: + break; + + default: + UnreachableCode(); + break; + } + + return result; } - - return result; } void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const Value& address, const Value& value) { AddPendingCycles(true); - Value result = m_register_cache.AllocateScratch(RegSize_32); - - switch (value.size) + if (g_settings.cpu_recompiler_memory_exceptions) { - case RegSize_8: - EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value); - break; + Value result = m_register_cache.AllocateScratch(RegSize_32); + switch (value.size) + { + case RegSize_8: + EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value); + break; - case RegSize_16: - EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value); - break; + case RegSize_16: + EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value); + break; - case RegSize_32: - EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value); - break; + case RegSize_32: + EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value); + break; - default: - UnreachableCode(); - break; + default: + UnreachableCode(); + break; + } + + m_register_cache.PushState(); + + a64::Label store_okay; + m_emit->Cbz(GetHostReg64(result.host_reg), &store_okay); + EmitBranch(GetCurrentFarCodePointer()); + m_emit->Bind(&store_okay); + + // store exception path + SwitchToFarCode(); + + // cause_bits = (result << 2) | BD | cop_n + m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2); + EmitOr(result.host_reg, result.host_reg, + Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException( + static_cast(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n))); + EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); + + EmitExceptionExit(); + SwitchToNearCode(); + + m_register_cache.PopState(); } + else + { + switch (value.size) + { + case RegSize_8: + EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryByte, address, value); + break; - m_register_cache.PushState(); + case RegSize_16: + EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryHalfWord, address, value); + break; - a64::Label store_okay; - m_emit->Cbz(GetHostReg64(result.host_reg), &store_okay); - EmitBranch(GetCurrentFarCodePointer()); - m_emit->Bind(&store_okay); + case RegSize_32: + EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryWord, address, value); + break; - // store exception path - SwitchToFarCode(); - - // cause_bits = (result << 2) | BD | cop_n - m_emit->lsl(GetHostReg32(result.host_reg), GetHostReg32(result.host_reg), 2); - EmitOr(result.host_reg, result.host_reg, - Value::FromConstantU32(Cop0Registers::CAUSE::MakeValueForException( - static_cast(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n))); - EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); - - EmitExceptionExit(); - SwitchToNearCode(); - - m_register_cache.PopState(); + default: + UnreachableCode(); + break; + } + } } void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr) diff --git a/src/core/cpu_recompiler_code_generator_x64.cpp b/src/core/cpu_recompiler_code_generator_x64.cpp index f620872ca..bc41b71cf 100644 --- a/src/core/cpu_recompiler_code_generator_x64.cpp +++ b/src/core/cpu_recompiler_code_generator_x64.cpp @@ -3,6 +3,7 @@ #include "cpu_core_private.h" #include "cpu_recompiler_code_generator.h" #include "cpu_recompiler_thunks.h" +#include "settings.h" namespace CPU::Recompiler { @@ -1739,117 +1740,186 @@ Value CodeGenerator::EmitLoadGuestMemory(const CodeBlockInstruction& cbi, const { AddPendingCycles(true); - // We need to use the full 64 bits here since we test the sign bit result. - Value result = m_register_cache.AllocateScratch(RegSize_64); - - // NOTE: This can leave junk in the upper bits - switch (size) + if (g_settings.cpu_recompiler_memory_exceptions) { - case RegSize_8: - EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address); - break; + // We need to use the full 64 bits here since we test the sign bit result. + Value result = m_register_cache.AllocateScratch(RegSize_64); - case RegSize_16: - EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address); - break; + // NOTE: This can leave junk in the upper bits + switch (size) + { + case RegSize_8: + EmitFunctionCall(&result, &Thunks::ReadMemoryByte, address); + break; - case RegSize_32: - EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address); - break; + case RegSize_16: + EmitFunctionCall(&result, &Thunks::ReadMemoryHalfWord, address); + break; - default: - UnreachableCode(); - break; + case RegSize_32: + EmitFunctionCall(&result, &Thunks::ReadMemoryWord, address); + break; + + default: + UnreachableCode(); + break; + } + + m_emit->test(GetHostReg64(result.host_reg), GetHostReg64(result.host_reg)); + m_emit->js(GetCurrentFarCodePointer()); + + m_register_cache.PushState(); + + // load exception path + SwitchToFarCode(); + + // cause_bits = (-result << 2) | BD | cop_n + m_emit->neg(GetHostReg32(result.host_reg)); + m_emit->shl(GetHostReg32(result.host_reg), 2); + m_emit->or_(GetHostReg32(result.host_reg), + Cop0Registers::CAUSE::MakeValueForException(static_cast(0), cbi.is_branch_delay_slot, false, + cbi.instruction.cop.cop_n)); + EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); + + EmitExceptionExit(); + SwitchToNearCode(); + + m_register_cache.PopState(); + + // Downcast to ignore upper 56/48/32 bits. This should be a noop. + switch (size) + { + case RegSize_8: + ConvertValueSizeInPlace(&result, RegSize_8, false); + break; + + case RegSize_16: + ConvertValueSizeInPlace(&result, RegSize_16, false); + break; + + case RegSize_32: + ConvertValueSizeInPlace(&result, RegSize_32, false); + break; + + default: + UnreachableCode(); + break; + } + + return result; } - - m_emit->test(GetHostReg64(result.host_reg), GetHostReg64(result.host_reg)); - m_emit->js(GetCurrentFarCodePointer()); - - m_register_cache.PushState(); - - // load exception path - SwitchToFarCode(); - - // cause_bits = (-result << 2) | BD | cop_n - m_emit->neg(GetHostReg32(result.host_reg)); - m_emit->shl(GetHostReg32(result.host_reg), 2); - m_emit->or_(GetHostReg32(result.host_reg), - Cop0Registers::CAUSE::MakeValueForException(static_cast(0), cbi.is_branch_delay_slot, false, - cbi.instruction.cop.cop_n)); - EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); - - EmitExceptionExit(); - SwitchToNearCode(); - - m_register_cache.PopState(); - - // Downcast to ignore upper 56/48/32 bits. This should be a noop. - switch (size) + else { - case RegSize_8: - ConvertValueSizeInPlace(&result, RegSize_8, false); - break; + Value result = m_register_cache.AllocateScratch(RegSize_32); + switch (size) + { + case RegSize_8: + EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryByte, address); + break; - case RegSize_16: - ConvertValueSizeInPlace(&result, RegSize_16, false); - break; + case RegSize_16: + EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryHalfWord, address); + break; - case RegSize_32: - ConvertValueSizeInPlace(&result, RegSize_32, false); - break; + case RegSize_32: + EmitFunctionCall(&result, &Thunks::UncheckedReadMemoryWord, address); + break; - default: - UnreachableCode(); - break; + default: + UnreachableCode(); + break; + } + + // Downcast to ignore upper 56/48/32 bits. This should be a noop. + switch (size) + { + case RegSize_8: + ConvertValueSizeInPlace(&result, RegSize_8, false); + break; + + case RegSize_16: + ConvertValueSizeInPlace(&result, RegSize_16, false); + break; + + case RegSize_32: + break; + + default: + UnreachableCode(); + break; + } + + return result; } - - return result; } void CodeGenerator::EmitStoreGuestMemory(const CodeBlockInstruction& cbi, const Value& address, const Value& value) { AddPendingCycles(true); - Value result = m_register_cache.AllocateScratch(RegSize_32); - - switch (value.size) + if (g_settings.cpu_recompiler_memory_exceptions) { - case RegSize_8: - EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value); - break; + Value result = m_register_cache.AllocateScratch(RegSize_32); + switch (value.size) + { + case RegSize_8: + EmitFunctionCall(&result, &Thunks::WriteMemoryByte, address, value); + break; - case RegSize_16: - EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value); - break; + case RegSize_16: + EmitFunctionCall(&result, &Thunks::WriteMemoryHalfWord, address, value); + break; - case RegSize_32: - EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value); - break; + case RegSize_32: + EmitFunctionCall(&result, &Thunks::WriteMemoryWord, address, value); + break; - default: - UnreachableCode(); - break; + default: + UnreachableCode(); + break; + } + + m_register_cache.PushState(); + + m_emit->test(GetHostReg32(result), GetHostReg32(result)); + m_emit->jnz(GetCurrentFarCodePointer()); + + // store exception path + SwitchToFarCode(); + + // cause_bits = (result << 2) | BD | cop_n + m_emit->shl(GetHostReg32(result.host_reg), 2); + m_emit->or_(GetHostReg32(result.host_reg), + Cop0Registers::CAUSE::MakeValueForException(static_cast(0), cbi.is_branch_delay_slot, false, + cbi.instruction.cop.cop_n)); + EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); + + EmitExceptionExit(); + SwitchToNearCode(); + + m_register_cache.PopState(); } + else + { + switch (value.size) + { + case RegSize_8: + EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryByte, address, value); + break; - m_register_cache.PushState(); + case RegSize_16: + EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryHalfWord, address, value); + break; - m_emit->test(GetHostReg32(result), GetHostReg32(result)); - m_emit->jnz(GetCurrentFarCodePointer()); + case RegSize_32: + EmitFunctionCall(nullptr, &Thunks::UncheckedWriteMemoryWord, address, value); + break; - // store exception path - SwitchToFarCode(); - - // cause_bits = (result << 2) | BD | cop_n - m_emit->shl(GetHostReg32(result.host_reg), 2); - m_emit->or_(GetHostReg32(result.host_reg), - Cop0Registers::CAUSE::MakeValueForException(static_cast(0), cbi.is_branch_delay_slot, false, - cbi.instruction.cop.cop_n)); - EmitFunctionCall(nullptr, static_cast(&CPU::RaiseException), result, GetCurrentInstructionPC()); - - EmitExceptionExit(); - SwitchToNearCode(); - - m_register_cache.PopState(); + default: + UnreachableCode(); + break; + } + } } void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr) diff --git a/src/core/cpu_recompiler_thunks.h b/src/core/cpu_recompiler_thunks.h index 2ea1817dd..9b9316dde 100644 --- a/src/core/cpu_recompiler_thunks.h +++ b/src/core/cpu_recompiler_thunks.h @@ -22,6 +22,15 @@ u32 WriteMemoryByte(u32 address, u8 value); u32 WriteMemoryHalfWord(u32 address, u16 value); u32 WriteMemoryWord(u32 address, u32 value); +// Unchecked memory access variants. No alignment or bus exceptions. +u32 UncheckedReadMemoryByte(u32 address); +u32 UncheckedReadMemoryHalfWord(u32 address); +u32 UncheckedReadMemoryWord(u32 address); +void UncheckedWriteMemoryByte(u32 address, u8 value); +void UncheckedWriteMemoryHalfWord(u32 address, u16 value); +void UncheckedWriteMemoryWord(u32 address, u32 value); + + } // namespace Recompiler::Thunks } // namespace CPU \ No newline at end of file diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 2f9d6cdc6..66fa8f32a 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -358,6 +358,7 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetBoolValue("Main", "LoadDevicesFromSaveStates", false); si.SetStringValue("CPU", "ExecutionMode", Settings::GetCPUExecutionModeName(Settings::DEFAULT_CPU_EXECUTION_MODE)); + si.SetBoolValue("CPU", "RecompilerMemoryExceptions", false); si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(Settings::DEFAULT_GPU_RENDERER)); si.SetIntValue("GPU", "ResolutionScale", 1); @@ -474,6 +475,14 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) CPU::CodeCache::SetUseRecompiler(g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler); } + if (g_settings.cpu_execution_mode == CPUExecutionMode::Recompiler && + g_settings.cpu_recompiler_memory_exceptions != old_settings.cpu_recompiler_memory_exceptions) + { + ReportFormattedMessage("CPU memory exceptions %s, flushing all blocks.", + g_settings.cpu_recompiler_memory_exceptions ? "enabled" : "disabled"); + CPU::CodeCache::Flush(); + } + m_audio_stream->SetOutputVolume(g_settings.audio_output_muted ? 0 : g_settings.audio_output_volume); if (g_settings.gpu_resolution_scale != old_settings.gpu_resolution_scale || diff --git a/src/core/settings.cpp b/src/core/settings.cpp index dac561636..e4c7abfd8 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -89,6 +89,7 @@ void Settings::Load(SettingsInterface& si) ParseCPUExecutionMode( si.GetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(DEFAULT_CPU_EXECUTION_MODE)).c_str()) .value_or(DEFAULT_CPU_EXECUTION_MODE); + cpu_recompiler_memory_exceptions = si.GetBoolValue("CPU", "RecompilerMemoryExceptions", false); gpu_renderer = ParseRendererName(si.GetStringValue("GPU", "Renderer", GetRendererName(DEFAULT_GPU_RENDERER)).c_str()) .value_or(DEFAULT_GPU_RENDERER); @@ -196,6 +197,7 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Main", "LoadDevicesFromSaveStates", load_devices_from_save_states); si.SetStringValue("CPU", "ExecutionMode", GetCPUExecutionModeName(cpu_execution_mode)); + si.SetBoolValue("CPU", "RecompilerMemoryExceptions", cpu_recompiler_memory_exceptions); si.SetStringValue("GPU", "Renderer", GetRendererName(gpu_renderer)); si.SetStringValue("GPU", "Adapter", gpu_adapter.c_str()); diff --git a/src/core/settings.h b/src/core/settings.h index d0a010811..4aa068289 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -68,6 +68,7 @@ struct Settings ConsoleRegion region = ConsoleRegion::Auto; CPUExecutionMode cpu_execution_mode = CPUExecutionMode::Interpreter; + bool cpu_recompiler_memory_exceptions = false; float emulation_speed = 1.0f; bool speed_limiter_enabled = true; diff --git a/src/duckstation-qt/advancedsettingswidget.cpp b/src/duckstation-qt/advancedsettingswidget.cpp index 4f2889b88..0222116ea 100644 --- a/src/duckstation-qt/advancedsettingswidget.cpp +++ b/src/duckstation-qt/advancedsettingswidget.cpp @@ -23,6 +23,8 @@ AdvancedSettingsWidget::AdvancedSettingsWidget(QtHostInterface* host_interface, SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.dmaHaltTicks, "Hacks", "DMAHaltTicks"); SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuFIFOSize, "Hacks", "GPUFIFOSize"); SettingWidgetBinder::BindWidgetToIntSetting(m_host_interface, m_ui.gpuMaxRunAhead, "Hacks", "GPUMaxRunAhead"); + SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, m_ui.cpuRecompilerMemoryExceptions, "CPU", + "RecompilerMemoryExceptions", false); connect(m_ui.resetToDefaultButton, &QPushButton::clicked, this, &AdvancedSettingsWidget::onResetToDefaultClicked); } @@ -35,4 +37,5 @@ void AdvancedSettingsWidget::onResetToDefaultClicked() m_ui.dmaHaltTicks->setValue(static_cast(Settings::DEFAULT_DMA_HALT_TICKS)); m_ui.gpuFIFOSize->setValue(static_cast(Settings::DEFAULT_GPU_FIFO_SIZE)); m_ui.gpuMaxRunAhead->setValue(static_cast(Settings::DEFAULT_GPU_MAX_RUN_AHEAD)); + m_ui.cpuRecompilerMemoryExceptions->setChecked(false); } diff --git a/src/duckstation-qt/advancedsettingswidget.ui b/src/duckstation-qt/advancedsettingswidget.ui index 485ac6152..67b914d43 100644 --- a/src/duckstation-qt/advancedsettingswidget.ui +++ b/src/duckstation-qt/advancedsettingswidget.ui @@ -184,13 +184,20 @@ - + Reset To Default + + + + Enable Recompiler Memory Exceptions + + + diff --git a/src/duckstation-sdl/sdl_host_interface.cpp b/src/duckstation-sdl/sdl_host_interface.cpp index 9d1d0177d..7a874465d 100644 --- a/src/duckstation-sdl/sdl_host_interface.cpp +++ b/src/duckstation-sdl/sdl_host_interface.cpp @@ -1231,6 +1231,9 @@ void SDLHostInterface::DrawSettingsWindow() settings_changed = true; } + settings_changed |= + ImGui::Checkbox("Enable Recompiler Memory Exceptions", &m_settings_copy.cpu_recompiler_memory_exceptions); + ImGui::EndTabItem(); }