From 1d5f810a4b7aa1b6b38c7cd449743f943ef122dc Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Sat, 8 Aug 2020 16:44:12 +1000 Subject: [PATCH] CPU/Recompiler: Disable memory access exceptions by default This means it'll no longer pass amidog's CPU test in the default config. But no games rely on this. You can enable it in advanced options if you want to pass the CPU test. --- src/core/bus.cpp | 50 +++- src/core/cpu_code_cache.cpp | 1 + src/core/cpu_core.cpp | 27 -- src/core/cpu_core.h | 2 - src/core/cpu_core_private.h | 27 ++ src/core/cpu_recompiler_code_generator.cpp | 3 +- .../cpu_recompiler_code_generator_aarch64.cpp | 252 +++++++++++------- .../cpu_recompiler_code_generator_x64.cpp | 244 +++++++++++------ src/core/cpu_recompiler_thunks.h | 9 + src/core/host_interface.cpp | 9 + src/core/settings.cpp | 2 + src/core/settings.h | 1 + src/duckstation-qt/advancedsettingswidget.cpp | 3 + src/duckstation-qt/advancedsettingswidget.ui | 9 +- src/duckstation-sdl/sdl_host_interface.cpp | 3 + 15 files changed, 430 insertions(+), 212 deletions(-) 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(); }