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.
This commit is contained in:
Connor McLaughlin 2020-08-08 16:44:12 +10:00
parent b1377fe0d9
commit 1d5f810a4b
15 changed files with 430 additions and 212 deletions

View file

@ -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<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
if (cycles < 0)
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
@ -1123,7 +1129,7 @@ u64 ReadMemoryHalfWord(u32 address)
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
}
u32 temp = 0;
u32 temp;
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
if (cycles < 0)
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
@ -1140,7 +1146,7 @@ u64 ReadMemoryWord(u32 address)
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
}
u32 temp = 0;
u32 temp;
const TickCount cycles = DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, temp);
if (cycles < 0)
return static_cast<u64>(-static_cast<s64>(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<MemoryAccessType::Read, MemoryAccessSize::Byte>(address, temp);
return temp;
}
u32 UncheckedReadMemoryHalfWord(u32 address)
{
u32 temp;
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(address, temp);
return temp;
}
u32 UncheckedReadMemoryWord(u32 address)
{
u32 temp;
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(address, temp);
return temp;
}
void UncheckedWriteMemoryByte(u32 address, u8 value)
{
u32 temp = ZeroExtend32(value);
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Byte>(address, temp);
}
void UncheckedWriteMemoryHalfWord(u32 address, u16 value)
{
u32 temp = ZeroExtend32(value);
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::HalfWord>(address, temp);
}
void UncheckedWriteMemoryWord(u32 address, u32 value)
{
g_state.pending_ticks += DoMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(address, value);
}
} // namespace Recompiler::Thunks
} // namespace CPU

View file

@ -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"

View file

@ -240,33 +240,6 @@ void ClearExternalInterrupt(u8 bit)
g_state.cop0_regs.cause.Ip &= static_cast<u8>(~(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

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&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<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&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<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&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<Exception>(0), cbi.is_branch_delay_slot, false, cbi.instruction.cop.cop_n)));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
EmitExceptionExit();
SwitchToNearCode();
m_register_cache.PopState();
default:
UnreachableCode();
break;
}
}
}
void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr)

View file

@ -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<Exception>(0), cbi.is_branch_delay_slot, false,
cbi.instruction.cop.cop_n));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&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<Exception>(0), cbi.is_branch_delay_slot, false,
cbi.instruction.cop.cop_n));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&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<Exception>(0), cbi.is_branch_delay_slot, false,
cbi.instruction.cop.cop_n));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&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<Exception>(0), cbi.is_branch_delay_slot, false,
cbi.instruction.cop.cop_n));
EmitFunctionCall(nullptr, static_cast<void (*)(u32, u32)>(&CPU::RaiseException), result, GetCurrentInstructionPC());
EmitExceptionExit();
SwitchToNearCode();
m_register_cache.PopState();
default:
UnreachableCode();
break;
}
}
}
void CodeGenerator::EmitLoadGlobal(HostReg host_reg, RegSize size, const void* ptr)

View file

@ -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

View file

@ -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 ||

View file

@ -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());

View file

@ -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;

View file

@ -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<int>(Settings::DEFAULT_DMA_HALT_TICKS));
m_ui.gpuFIFOSize->setValue(static_cast<int>(Settings::DEFAULT_GPU_FIFO_SIZE));
m_ui.gpuMaxRunAhead->setValue(static_cast<int>(Settings::DEFAULT_GPU_MAX_RUN_AHEAD));
m_ui.cpuRecompilerMemoryExceptions->setChecked(false);
}

View file

@ -184,13 +184,20 @@
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<item row="6" column="0" colspan="2">
<widget class="QPushButton" name="resetToDefaultButton">
<property name="text">
<string>Reset To Default</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="cpuRecompilerMemoryExceptions">
<property name="text">
<string>Enable Recompiler Memory Exceptions</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -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();
}