mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 13:55:38 +00:00
CPU: Use lookup tables for memory access
This commit is contained in:
parent
05fe925409
commit
56fc207af6
1823
src/core/bus.cpp
1823
src/core/bus.cpp
File diff suppressed because it is too large
Load diff
|
@ -23,9 +23,12 @@ enum : u32
|
||||||
RAM_8MB_SIZE = 0x800000,
|
RAM_8MB_SIZE = 0x800000,
|
||||||
RAM_8MB_MASK = RAM_8MB_SIZE - 1,
|
RAM_8MB_MASK = RAM_8MB_SIZE - 1,
|
||||||
RAM_MIRROR_END = 0x800000,
|
RAM_MIRROR_END = 0x800000,
|
||||||
|
RAM_MIRROR_SIZE = 0x800000,
|
||||||
EXP1_BASE = 0x1F000000,
|
EXP1_BASE = 0x1F000000,
|
||||||
EXP1_SIZE = 0x800000,
|
EXP1_SIZE = 0x800000,
|
||||||
EXP1_MASK = EXP1_SIZE - 1,
|
EXP1_MASK = EXP1_SIZE - 1,
|
||||||
|
HW_BASE = 0x1F801000,
|
||||||
|
HW_SIZE = 0x1000,
|
||||||
MEMCTRL_BASE = 0x1F801000,
|
MEMCTRL_BASE = 0x1F801000,
|
||||||
MEMCTRL_SIZE = 0x40,
|
MEMCTRL_SIZE = 0x40,
|
||||||
MEMCTRL_MASK = MEMCTRL_SIZE - 1,
|
MEMCTRL_MASK = MEMCTRL_SIZE - 1,
|
||||||
|
@ -38,9 +41,9 @@ enum : u32
|
||||||
MEMCTRL2_BASE = 0x1F801060,
|
MEMCTRL2_BASE = 0x1F801060,
|
||||||
MEMCTRL2_SIZE = 0x10,
|
MEMCTRL2_SIZE = 0x10,
|
||||||
MEMCTRL2_MASK = MEMCTRL2_SIZE - 1,
|
MEMCTRL2_MASK = MEMCTRL2_SIZE - 1,
|
||||||
INTERRUPT_CONTROLLER_BASE = 0x1F801070,
|
INTC_BASE = 0x1F801070,
|
||||||
INTERRUPT_CONTROLLER_SIZE = 0x10,
|
INTC_SIZE = 0x10,
|
||||||
INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1,
|
INTERRUPT_CONTROLLER_MASK = INTC_SIZE - 1,
|
||||||
DMA_BASE = 0x1F801080,
|
DMA_BASE = 0x1F801080,
|
||||||
DMA_SIZE = 0x80,
|
DMA_SIZE = 0x80,
|
||||||
DMA_MASK = DMA_SIZE - 1,
|
DMA_MASK = DMA_SIZE - 1,
|
||||||
|
@ -63,7 +66,7 @@ enum : u32
|
||||||
EXP2_SIZE = 0x2000,
|
EXP2_SIZE = 0x2000,
|
||||||
EXP2_MASK = EXP2_SIZE - 1,
|
EXP2_MASK = EXP2_SIZE - 1,
|
||||||
EXP3_BASE = 0x1FA00000,
|
EXP3_BASE = 0x1FA00000,
|
||||||
EXP3_SIZE = 0x1,
|
EXP3_SIZE = 0x200000,
|
||||||
EXP3_MASK = EXP3_SIZE - 1,
|
EXP3_MASK = EXP3_SIZE - 1,
|
||||||
BIOS_BASE = 0x1FC00000,
|
BIOS_BASE = 0x1FC00000,
|
||||||
BIOS_SIZE = 0x80000,
|
BIOS_SIZE = 0x80000,
|
||||||
|
@ -85,6 +88,11 @@ enum : u32
|
||||||
RAM_2MB_CODE_PAGE_COUNT = (RAM_2MB_SIZE + (HOST_PAGE_SIZE + 1)) / HOST_PAGE_SIZE,
|
RAM_2MB_CODE_PAGE_COUNT = (RAM_2MB_SIZE + (HOST_PAGE_SIZE + 1)) / HOST_PAGE_SIZE,
|
||||||
RAM_8MB_CODE_PAGE_COUNT = (RAM_8MB_SIZE + (HOST_PAGE_SIZE + 1)) / HOST_PAGE_SIZE,
|
RAM_8MB_CODE_PAGE_COUNT = (RAM_8MB_SIZE + (HOST_PAGE_SIZE + 1)) / HOST_PAGE_SIZE,
|
||||||
|
|
||||||
|
MEMORY_LUT_PAGE_SIZE = 4096,
|
||||||
|
MEMORY_LUT_PAGE_SHIFT = 12,
|
||||||
|
MEMORY_LUT_SIZE = 0x100000, // 0x100000000 >> 12
|
||||||
|
MEMORY_LUT_SLOTS = MEMORY_LUT_SIZE * 3 * 2, // [size][read_write]
|
||||||
|
|
||||||
FASTMEM_LUT_PAGE_SIZE = 4096,
|
FASTMEM_LUT_PAGE_SIZE = 4096,
|
||||||
FASTMEM_LUT_PAGE_MASK = FASTMEM_LUT_PAGE_SIZE - 1,
|
FASTMEM_LUT_PAGE_MASK = FASTMEM_LUT_PAGE_SIZE - 1,
|
||||||
FASTMEM_LUT_PAGE_SHIFT = 12,
|
FASTMEM_LUT_PAGE_SHIFT = 12,
|
||||||
|
@ -105,8 +113,20 @@ void Shutdown();
|
||||||
void Reset();
|
void Reset();
|
||||||
bool DoState(StateWrapper& sw);
|
bool DoState(StateWrapper& sw);
|
||||||
|
|
||||||
|
using MemoryReadHandler = u32 (*)(VirtualMemoryAddress address);
|
||||||
|
using MemoryWriteHandler = void (*)(VirtualMemoryAddress, u32);
|
||||||
|
|
||||||
|
void** GetMemoryHandlers(bool isolate_cache, bool swap_caches);
|
||||||
|
|
||||||
|
template<typename FP>
|
||||||
|
ALWAYS_INLINE_RELEASE static FP* OffsetHandlerArray(void** handlers, MemoryAccessSize size, MemoryAccessType type)
|
||||||
|
{
|
||||||
|
return reinterpret_cast<FP*>(handlers +
|
||||||
|
(((static_cast<size_t>(size) * 2) + static_cast<size_t>(type)) * MEMORY_LUT_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
CPUFastmemMode GetFastmemMode();
|
CPUFastmemMode GetFastmemMode();
|
||||||
u8* GetFastmemBase();
|
void* GetFastmemBase();
|
||||||
void UpdateFastmemViews(CPUFastmemMode mode);
|
void UpdateFastmemViews(CPUFastmemMode mode);
|
||||||
bool CanUseFastmemForAddress(VirtualMemoryAddress address);
|
bool CanUseFastmemForAddress(VirtualMemoryAddress address);
|
||||||
|
|
||||||
|
@ -116,7 +136,12 @@ extern std::bitset<RAM_8MB_CODE_PAGE_COUNT> g_ram_code_bits;
|
||||||
extern u8* g_ram; // 2MB-8MB RAM
|
extern u8* g_ram; // 2MB-8MB RAM
|
||||||
extern u32 g_ram_size; // Active size of RAM.
|
extern u32 g_ram_size; // Active size of RAM.
|
||||||
extern u32 g_ram_mask; // Active address bits for RAM.
|
extern u32 g_ram_mask; // Active address bits for RAM.
|
||||||
extern u8 g_bios[BIOS_SIZE]; // 512K BIOS ROM
|
extern u8* g_bios; // 512K BIOS ROM
|
||||||
|
extern std::array<TickCount, 3> g_exp1_access_time;
|
||||||
|
extern std::array<TickCount, 3> g_exp2_access_time;
|
||||||
|
extern std::array<TickCount, 3> g_bios_access_time;
|
||||||
|
extern std::array<TickCount, 3> g_cdrom_access_time;
|
||||||
|
extern std::array<TickCount, 3> g_spu_access_time;
|
||||||
|
|
||||||
/// Returns true if the address specified is writable (RAM).
|
/// Returns true if the address specified is writable (RAM).
|
||||||
ALWAYS_INLINE static bool IsRAMAddress(PhysicalMemoryAddress address)
|
ALWAYS_INLINE static bool IsRAMAddress(PhysicalMemoryAddress address)
|
||||||
|
|
|
@ -265,7 +265,6 @@ void Initialize()
|
||||||
|
|
||||||
AllocateFastMap();
|
AllocateFastMap();
|
||||||
|
|
||||||
|
|
||||||
#ifdef ENABLE_RECOMPILER
|
#ifdef ENABLE_RECOMPILER
|
||||||
if (g_settings.IsUsingRecompiler())
|
if (g_settings.IsUsingRecompiler())
|
||||||
{
|
{
|
||||||
|
@ -531,7 +530,9 @@ void Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
void __debugbreak() {}
|
void __debugbreak()
|
||||||
|
{
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void LogCurrentState()
|
void LogCurrentState()
|
||||||
|
@ -1127,7 +1128,7 @@ bool InitializeFastmem()
|
||||||
}
|
}
|
||||||
|
|
||||||
Bus::UpdateFastmemViews(mode);
|
Bus::UpdateFastmemViews(mode);
|
||||||
CPU::UpdateFastmemBase();
|
CPU::UpdateMemoryPointers();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1135,21 +1136,22 @@ void ShutdownFastmem()
|
||||||
{
|
{
|
||||||
Common::PageFaultHandler::RemoveHandler(&s_host_code_map);
|
Common::PageFaultHandler::RemoveHandler(&s_host_code_map);
|
||||||
Bus::UpdateFastmemViews(CPUFastmemMode::Disabled);
|
Bus::UpdateFastmemViews(CPUFastmemMode::Disabled);
|
||||||
CPU::UpdateFastmemBase();
|
CPU::UpdateMemoryPointers();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_MMAP_FASTMEM
|
#ifdef ENABLE_MMAP_FASTMEM
|
||||||
|
|
||||||
Common::PageFaultHandler::HandlerResult MMapPageFaultHandler(void* exception_pc, void* fault_address, bool is_write)
|
Common::PageFaultHandler::HandlerResult MMapPageFaultHandler(void* exception_pc, void* fault_address, bool is_write)
|
||||||
{
|
{
|
||||||
if (static_cast<u8*>(fault_address) < g_state.fastmem_base ||
|
if (static_cast<u8*>(fault_address) < static_cast<u8*>(g_state.fastmem_base) ||
|
||||||
(static_cast<u8*>(fault_address) - g_state.fastmem_base) >= static_cast<ptrdiff_t>(Bus::FASTMEM_ARENA_SIZE))
|
(static_cast<u8*>(fault_address) - static_cast<u8*>(g_state.fastmem_base)) >=
|
||||||
|
static_cast<ptrdiff_t>(Bus::FASTMEM_ARENA_SIZE))
|
||||||
{
|
{
|
||||||
return Common::PageFaultHandler::HandlerResult::ExecuteNextHandler;
|
return Common::PageFaultHandler::HandlerResult::ExecuteNextHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
const PhysicalMemoryAddress fastmem_address =
|
const PhysicalMemoryAddress fastmem_address = static_cast<PhysicalMemoryAddress>(
|
||||||
static_cast<PhysicalMemoryAddress>(static_cast<ptrdiff_t>(static_cast<u8*>(fault_address) - g_state.fastmem_base));
|
static_cast<ptrdiff_t>(static_cast<u8*>(fault_address) - static_cast<u8*>(g_state.fastmem_base)));
|
||||||
|
|
||||||
Log_DevPrintf("Page fault handler invoked at PC=%p Address=%p %s, fastmem offset 0x%08X", exception_pc, fault_address,
|
Log_DevPrintf("Page fault handler invoked at PC=%p Address=%p %s, fastmem offset 0x%08X", exception_pc, fault_address,
|
||||||
is_write ? "(write)" : "(read)", fastmem_address);
|
is_write ? "(write)" : "(read)", fastmem_address);
|
||||||
|
|
|
@ -63,6 +63,21 @@ static void ExecuteInstruction();
|
||||||
template<PGXPMode pgxp_mode, bool debug>
|
template<PGXPMode pgxp_mode, bool debug>
|
||||||
[[noreturn]] static void ExecuteImpl();
|
[[noreturn]] static void ExecuteImpl();
|
||||||
|
|
||||||
|
static bool FetchInstruction();
|
||||||
|
static bool FetchInstructionForInterpreterFallback();
|
||||||
|
template<bool add_ticks, bool icache_read = false, u32 word_count = 1, bool raise_exceptions>
|
||||||
|
static bool DoInstructionRead(PhysicalMemoryAddress address, void* data);
|
||||||
|
template<MemoryAccessType type, MemoryAccessSize size>
|
||||||
|
static bool DoSafeMemoryAccess(VirtualMemoryAddress address, u32& value);
|
||||||
|
template<MemoryAccessType type, MemoryAccessSize size>
|
||||||
|
static bool DoAlignmentCheck(VirtualMemoryAddress address);
|
||||||
|
static bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value);
|
||||||
|
static bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value);
|
||||||
|
static bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value);
|
||||||
|
static bool WriteMemoryByte(VirtualMemoryAddress addr, u32 value);
|
||||||
|
static bool WriteMemoryHalfWord(VirtualMemoryAddress addr, u32 value);
|
||||||
|
static bool WriteMemoryWord(VirtualMemoryAddress addr, u32 value);
|
||||||
|
|
||||||
State g_state;
|
State g_state;
|
||||||
bool TRACE_EXECUTION = false;
|
bool TRACE_EXECUTION = false;
|
||||||
|
|
||||||
|
@ -139,7 +154,7 @@ void CPU::Initialize()
|
||||||
s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC;
|
s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC;
|
||||||
s_single_step = false;
|
s_single_step = false;
|
||||||
|
|
||||||
UpdateFastmemBase();
|
UpdateMemoryPointers();
|
||||||
|
|
||||||
GTE::Initialize();
|
GTE::Initialize();
|
||||||
}
|
}
|
||||||
|
@ -154,6 +169,8 @@ void CPU::Reset()
|
||||||
{
|
{
|
||||||
g_state.pending_ticks = 0;
|
g_state.pending_ticks = 0;
|
||||||
g_state.downcount = 0;
|
g_state.downcount = 0;
|
||||||
|
g_state.exception_raised = false;
|
||||||
|
g_state.bus_error = false;
|
||||||
|
|
||||||
g_state.regs = {};
|
g_state.regs = {};
|
||||||
|
|
||||||
|
@ -168,7 +185,7 @@ void CPU::Reset()
|
||||||
g_state.cop0_regs.cause.bits = 0;
|
g_state.cop0_regs.cause.bits = 0;
|
||||||
|
|
||||||
ClearICache();
|
ClearICache();
|
||||||
UpdateFastmemBase();
|
UpdateMemoryPointers();
|
||||||
|
|
||||||
GTE::Reset();
|
GTE::Reset();
|
||||||
|
|
||||||
|
@ -202,6 +219,7 @@ bool CPU::DoState(StateWrapper& sw)
|
||||||
sw.Do(&g_state.next_instruction_is_branch_delay_slot);
|
sw.Do(&g_state.next_instruction_is_branch_delay_slot);
|
||||||
sw.Do(&g_state.branch_was_taken);
|
sw.Do(&g_state.branch_was_taken);
|
||||||
sw.Do(&g_state.exception_raised);
|
sw.Do(&g_state.exception_raised);
|
||||||
|
sw.DoEx(&g_state.bus_error, 61, false);
|
||||||
if (sw.GetVersion() < 59)
|
if (sw.GetVersion() < 59)
|
||||||
{
|
{
|
||||||
bool interrupt_delay;
|
bool interrupt_delay;
|
||||||
|
@ -240,21 +258,13 @@ bool CPU::DoState(StateWrapper& sw)
|
||||||
|
|
||||||
if (sw.IsReading())
|
if (sw.IsReading())
|
||||||
{
|
{
|
||||||
UpdateFastmemBase();
|
UpdateMemoryPointers();
|
||||||
g_state.gte_completion_tick = 0;
|
g_state.gte_completion_tick = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !sw.HasError();
|
return !sw.HasError();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::UpdateFastmemBase()
|
|
||||||
{
|
|
||||||
if (g_state.cop0_regs.sr.Isc)
|
|
||||||
g_state.fastmem_base = nullptr;
|
|
||||||
else
|
|
||||||
g_state.fastmem_base = Bus::GetFastmemBase();
|
|
||||||
}
|
|
||||||
|
|
||||||
ALWAYS_INLINE_RELEASE void CPU::SetPC(u32 new_pc)
|
ALWAYS_INLINE_RELEASE void CPU::SetPC(u32 new_pc)
|
||||||
{
|
{
|
||||||
DebugAssert(Common::IsAlignedPow2(new_pc, 4));
|
DebugAssert(Common::IsAlignedPow2(new_pc, 4));
|
||||||
|
@ -527,6 +537,7 @@ ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value)
|
||||||
g_state.cop0_regs.sr.bits =
|
g_state.cop0_regs.sr.bits =
|
||||||
(g_state.cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | (value & Cop0Registers::SR::WRITE_MASK);
|
(g_state.cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | (value & Cop0Registers::SR::WRITE_MASK);
|
||||||
Log_DebugPrintf("COP0 SR <- %08X (now %08X)", value, g_state.cop0_regs.sr.bits);
|
Log_DebugPrintf("COP0 SR <- %08X (now %08X)", value, g_state.cop0_regs.sr.bits);
|
||||||
|
UpdateMemoryPointers();
|
||||||
CheckForPendingInterrupt();
|
CheckForPendingInterrupt();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -2357,3 +2368,904 @@ bool CPU::Recompiler::Thunks::InterpretInstructionPGXP()
|
||||||
ExecuteInstruction<PGXPMode::Memory, false>();
|
ExecuteInstruction<PGXPMode::Memory, false>();
|
||||||
return g_state.exception_raised;
|
return g_state.exception_raised;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE_RELEASE Bus::MemoryReadHandler CPU::GetMemoryReadHandler(VirtualMemoryAddress address,
|
||||||
|
MemoryAccessSize size)
|
||||||
|
{
|
||||||
|
Bus::MemoryReadHandler* base =
|
||||||
|
Bus::OffsetHandlerArray<Bus::MemoryReadHandler>(g_state.memory_handlers, size, MemoryAccessType::Read);
|
||||||
|
return base[address >> Bus::MEMORY_LUT_PAGE_SHIFT];
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE_RELEASE Bus::MemoryWriteHandler CPU::GetMemoryWriteHandler(VirtualMemoryAddress address,
|
||||||
|
MemoryAccessSize size)
|
||||||
|
{
|
||||||
|
Bus::MemoryWriteHandler* base =
|
||||||
|
Bus::OffsetHandlerArray<Bus::MemoryWriteHandler>(g_state.memory_handlers, size, MemoryAccessType::Write);
|
||||||
|
return base[address >> Bus::MEMORY_LUT_PAGE_SHIFT];
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::UpdateMemoryPointers()
|
||||||
|
{
|
||||||
|
g_state.memory_handlers = Bus::GetMemoryHandlers(g_state.cop0_regs.sr.Isc, g_state.cop0_regs.sr.Swc);
|
||||||
|
g_state.fastmem_base = g_state.cop0_regs.sr.Isc ? nullptr : Bus::GetFastmemBase();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::ExecutionModeChanged()
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<bool add_ticks, bool icache_read, u32 word_count, bool raise_exceptions>
|
||||||
|
ALWAYS_INLINE_RELEASE bool CPU::DoInstructionRead(PhysicalMemoryAddress address, void* data)
|
||||||
|
{
|
||||||
|
using namespace Bus;
|
||||||
|
|
||||||
|
address &= PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
|
||||||
|
if (address < RAM_MIRROR_END)
|
||||||
|
{
|
||||||
|
std::memcpy(data, &g_ram[address & g_ram_mask], sizeof(u32) * word_count);
|
||||||
|
if constexpr (add_ticks)
|
||||||
|
g_state.pending_ticks += (icache_read ? 1 : RAM_READ_TICKS) * word_count;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE))
|
||||||
|
{
|
||||||
|
std::memcpy(data, &g_bios[(address - BIOS_BASE) & BIOS_MASK], sizeof(u32) * word_count);
|
||||||
|
if constexpr (add_ticks)
|
||||||
|
g_state.pending_ticks += g_bios_access_time[static_cast<u32>(MemoryAccessSize::Word)] * word_count;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (raise_exceptions)
|
||||||
|
CPU::RaiseException(address, Cop0Registers::CAUSE::MakeValueForException(Exception::IBE, false, false, 0));
|
||||||
|
|
||||||
|
std::memset(data, 0, sizeof(u32) * word_count);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TickCount CPU::GetInstructionReadTicks(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
using namespace Bus;
|
||||||
|
|
||||||
|
address &= PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
|
||||||
|
if (address < RAM_MIRROR_END)
|
||||||
|
{
|
||||||
|
return RAM_READ_TICKS;
|
||||||
|
}
|
||||||
|
else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE))
|
||||||
|
{
|
||||||
|
return g_bios_access_time[static_cast<u32>(MemoryAccessSize::Word)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TickCount CPU::GetICacheFillTicks(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
using namespace Bus;
|
||||||
|
|
||||||
|
address &= PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
|
||||||
|
if (address < RAM_MIRROR_END)
|
||||||
|
{
|
||||||
|
return 1 * ((ICACHE_LINE_SIZE - (address & (ICACHE_LINE_SIZE - 1))) / sizeof(u32));
|
||||||
|
}
|
||||||
|
else if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE))
|
||||||
|
{
|
||||||
|
return g_bios_access_time[static_cast<u32>(MemoryAccessSize::Word)] *
|
||||||
|
((ICACHE_LINE_SIZE - (address & (ICACHE_LINE_SIZE - 1))) / sizeof(u32));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::CheckAndUpdateICacheTags(u32 line_count, TickCount uncached_ticks)
|
||||||
|
{
|
||||||
|
VirtualMemoryAddress current_pc = g_state.pc & ICACHE_TAG_ADDRESS_MASK;
|
||||||
|
if (IsCachedAddress(current_pc))
|
||||||
|
{
|
||||||
|
TickCount ticks = 0;
|
||||||
|
TickCount cached_ticks_per_line = GetICacheFillTicks(current_pc);
|
||||||
|
for (u32 i = 0; i < line_count; i++, current_pc += ICACHE_LINE_SIZE)
|
||||||
|
{
|
||||||
|
const u32 line = GetICacheLine(current_pc);
|
||||||
|
if (g_state.icache_tags[line] != current_pc)
|
||||||
|
{
|
||||||
|
g_state.icache_tags[line] = current_pc;
|
||||||
|
ticks += cached_ticks_per_line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_state.pending_ticks += ticks;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_state.pending_ticks += uncached_ticks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CPU::FillICache(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
const u32 line = GetICacheLine(address);
|
||||||
|
u8* line_data = &g_state.icache_data[line * ICACHE_LINE_SIZE];
|
||||||
|
u32 line_tag;
|
||||||
|
switch ((address >> 2) & 0x03u)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
DoInstructionRead<true, true, 4, false>(address & ~(ICACHE_LINE_SIZE - 1u), line_data);
|
||||||
|
line_tag = GetICacheTagForAddress(address);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
DoInstructionRead<true, true, 3, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0x4), line_data + 0x4);
|
||||||
|
line_tag = GetICacheTagForAddress(address) | 0x1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
DoInstructionRead<true, true, 2, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0x8), line_data + 0x8);
|
||||||
|
line_tag = GetICacheTagForAddress(address) | 0x3;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
default:
|
||||||
|
DoInstructionRead<true, true, 1, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0xC), line_data + 0xC);
|
||||||
|
line_tag = GetICacheTagForAddress(address) | 0x7;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
g_state.icache_tags[line] = line_tag;
|
||||||
|
|
||||||
|
const u32 offset = GetICacheLineOffset(address);
|
||||||
|
u32 result;
|
||||||
|
std::memcpy(&result, &line_data[offset], sizeof(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::ClearICache()
|
||||||
|
{
|
||||||
|
std::memset(g_state.icache_data.data(), 0, ICACHE_SIZE);
|
||||||
|
g_state.icache_tags.fill(ICACHE_INVALID_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace CPU {
|
||||||
|
ALWAYS_INLINE_RELEASE static u32 ReadICache(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
const u32 line = GetICacheLine(address);
|
||||||
|
const u8* line_data = &g_state.icache_data[line * ICACHE_LINE_SIZE];
|
||||||
|
const u32 offset = GetICacheLineOffset(address);
|
||||||
|
u32 result;
|
||||||
|
std::memcpy(&result, &line_data[offset], sizeof(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace CPU
|
||||||
|
|
||||||
|
ALWAYS_INLINE_RELEASE bool CPU::FetchInstruction()
|
||||||
|
{
|
||||||
|
DebugAssert(Common::IsAlignedPow2(g_state.npc, 4));
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress address = g_state.npc;
|
||||||
|
switch (address >> 29)
|
||||||
|
{
|
||||||
|
case 0x00: // KUSEG 0M-512M
|
||||||
|
case 0x04: // KSEG0 - physical memory cached
|
||||||
|
{
|
||||||
|
#if 0
|
||||||
|
DoInstructionRead<true, false, 1, false>(address, &g_state.next_instruction.bits);
|
||||||
|
#else
|
||||||
|
if (CompareICacheTag(address))
|
||||||
|
g_state.next_instruction.bits = ReadICache(address);
|
||||||
|
else
|
||||||
|
g_state.next_instruction.bits = FillICache(address);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x05: // KSEG1 - physical memory uncached
|
||||||
|
{
|
||||||
|
if (!DoInstructionRead<true, false, 1, true>(address, &g_state.next_instruction.bits))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: // KUSEG 512M-1024M
|
||||||
|
case 0x02: // KUSEG 1024M-1536M
|
||||||
|
case 0x03: // KUSEG 1536M-2048M
|
||||||
|
case 0x06: // KSEG2
|
||||||
|
case 0x07: // KSEG2
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
CPU::RaiseException(Cop0Registers::CAUSE::MakeValueForException(Exception::IBE,
|
||||||
|
g_state.current_instruction_in_branch_delay_slot,
|
||||||
|
g_state.current_instruction_was_branch_taken, 0),
|
||||||
|
address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_state.pc = g_state.npc;
|
||||||
|
g_state.npc += sizeof(g_state.next_instruction.bits);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::FetchInstructionForInterpreterFallback()
|
||||||
|
{
|
||||||
|
DebugAssert(Common::IsAlignedPow2(g_state.npc, 4));
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress address = g_state.npc;
|
||||||
|
switch (address >> 29)
|
||||||
|
{
|
||||||
|
case 0x00: // KUSEG 0M-512M
|
||||||
|
case 0x04: // KSEG0 - physical memory cached
|
||||||
|
case 0x05: // KSEG1 - physical memory uncached
|
||||||
|
{
|
||||||
|
// We don't use the icache when doing interpreter fallbacks, because it's probably stale.
|
||||||
|
if (!DoInstructionRead<false, false, 1, true>(address, &g_state.next_instruction.bits))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: // KUSEG 512M-1024M
|
||||||
|
case 0x02: // KUSEG 1024M-1536M
|
||||||
|
case 0x03: // KUSEG 1536M-2048M
|
||||||
|
case 0x06: // KSEG2
|
||||||
|
case 0x07: // KSEG2
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
CPU::RaiseException(Cop0Registers::CAUSE::MakeValueForException(Exception::IBE,
|
||||||
|
g_state.current_instruction_in_branch_delay_slot,
|
||||||
|
g_state.current_instruction_was_branch_taken, 0),
|
||||||
|
address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_state.pc = g_state.npc;
|
||||||
|
g_state.npc += sizeof(g_state.next_instruction.bits);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeReadInstruction(VirtualMemoryAddress addr, u32* value)
|
||||||
|
{
|
||||||
|
switch (addr >> 29)
|
||||||
|
{
|
||||||
|
case 0x00: // KUSEG 0M-512M
|
||||||
|
case 0x04: // KSEG0 - physical memory cached
|
||||||
|
case 0x05: // KSEG1 - physical memory uncached
|
||||||
|
{
|
||||||
|
// TODO: Check icache.
|
||||||
|
return DoInstructionRead<false, false, 1, false>(addr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x01: // KUSEG 512M-1024M
|
||||||
|
case 0x02: // KUSEG 1024M-1536M
|
||||||
|
case 0x03: // KUSEG 1536M-2048M
|
||||||
|
case 0x06: // KSEG2
|
||||||
|
case 0x07: // KSEG2
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<MemoryAccessType type, MemoryAccessSize size>
|
||||||
|
ALWAYS_INLINE bool CPU::DoSafeMemoryAccess(VirtualMemoryAddress address, u32& value)
|
||||||
|
{
|
||||||
|
using namespace Bus;
|
||||||
|
|
||||||
|
switch (address >> 29)
|
||||||
|
{
|
||||||
|
case 0x00: // KUSEG 0M-512M
|
||||||
|
case 0x04: // KSEG0 - physical memory cached
|
||||||
|
{
|
||||||
|
address &= PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
if ((address & DCACHE_LOCATION_MASK) == DCACHE_LOCATION)
|
||||||
|
{
|
||||||
|
const u32 offset = address & DCACHE_OFFSET_MASK;
|
||||||
|
|
||||||
|
if constexpr (type == MemoryAccessType::Read)
|
||||||
|
{
|
||||||
|
if constexpr (size == MemoryAccessSize::Byte)
|
||||||
|
{
|
||||||
|
value = CPU::g_state.dcache[offset];
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||||
|
{
|
||||||
|
u16 temp;
|
||||||
|
std::memcpy(&temp, &CPU::g_state.dcache[offset], sizeof(u16));
|
||||||
|
value = ZeroExtend32(temp);
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::Word)
|
||||||
|
{
|
||||||
|
std::memcpy(&value, &CPU::g_state.dcache[offset], sizeof(u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if constexpr (size == MemoryAccessSize::Byte)
|
||||||
|
{
|
||||||
|
CPU::g_state.dcache[offset] = Truncate8(value);
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||||
|
{
|
||||||
|
std::memcpy(&CPU::g_state.dcache[offset], &value, sizeof(u16));
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::Word)
|
||||||
|
{
|
||||||
|
std::memcpy(&CPU::g_state.dcache[offset], &value, sizeof(u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: // KUSEG 512M-1024M
|
||||||
|
case 0x02: // KUSEG 1024M-1536M
|
||||||
|
case 0x03: // KUSEG 1536M-2048M
|
||||||
|
case 0x06: // KSEG2
|
||||||
|
case 0x07: // KSEG2
|
||||||
|
{
|
||||||
|
// Above 512mb raises an exception.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0x05: // KSEG1 - physical memory uncached
|
||||||
|
{
|
||||||
|
address &= PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address < RAM_MIRROR_END)
|
||||||
|
{
|
||||||
|
const u32 offset = address & g_ram_mask;
|
||||||
|
if constexpr (type == MemoryAccessType::Read)
|
||||||
|
{
|
||||||
|
if constexpr (size == MemoryAccessSize::Byte)
|
||||||
|
{
|
||||||
|
value = g_ram[offset];
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||||
|
{
|
||||||
|
u16 temp;
|
||||||
|
std::memcpy(&temp, &g_ram[offset], sizeof(temp));
|
||||||
|
value = ZeroExtend32(temp);
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::Word)
|
||||||
|
{
|
||||||
|
std::memcpy(&value, &g_ram[offset], sizeof(u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const u32 page_index = offset / HOST_PAGE_SIZE;
|
||||||
|
|
||||||
|
if constexpr (size == MemoryAccessSize::Byte)
|
||||||
|
{
|
||||||
|
if (g_ram[offset] != Truncate8(value))
|
||||||
|
{
|
||||||
|
g_ram[offset] = Truncate8(value);
|
||||||
|
if (g_ram_code_bits[page_index])
|
||||||
|
CPU::CodeCache::InvalidateBlocksWithPageIndex(page_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||||
|
{
|
||||||
|
const u16 new_value = Truncate16(value);
|
||||||
|
u16 old_value;
|
||||||
|
std::memcpy(&old_value, &g_ram[offset], sizeof(old_value));
|
||||||
|
if (old_value != new_value)
|
||||||
|
{
|
||||||
|
std::memcpy(&g_ram[offset], &new_value, sizeof(u16));
|
||||||
|
if (g_ram_code_bits[page_index])
|
||||||
|
CPU::CodeCache::InvalidateBlocksWithPageIndex(page_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::Word)
|
||||||
|
{
|
||||||
|
u32 old_value;
|
||||||
|
std::memcpy(&old_value, &g_ram[offset], sizeof(u32));
|
||||||
|
if (old_value != value)
|
||||||
|
{
|
||||||
|
std::memcpy(&g_ram[offset], &value, sizeof(u32));
|
||||||
|
if (g_ram_code_bits[page_index])
|
||||||
|
CPU::CodeCache::InvalidateBlocksWithPageIndex(page_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if constexpr (type == MemoryAccessType::Read)
|
||||||
|
{
|
||||||
|
if (address >= BIOS_BASE && address < (BIOS_BASE + BIOS_SIZE))
|
||||||
|
{
|
||||||
|
const u32 offset = (address & BIOS_MASK);
|
||||||
|
if constexpr (size == MemoryAccessSize::Byte)
|
||||||
|
{
|
||||||
|
value = ZeroExtend32(g_bios[offset]);
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::HalfWord)
|
||||||
|
{
|
||||||
|
u16 halfword;
|
||||||
|
std::memcpy(&halfword, &g_bios[offset], sizeof(u16));
|
||||||
|
value = ZeroExtend32(halfword);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::memcpy(&value, &g_bios[offset], sizeof(u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeReadMemoryByte(VirtualMemoryAddress addr, u8* value)
|
||||||
|
{
|
||||||
|
u32 temp = 0;
|
||||||
|
if (!DoSafeMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Byte>(addr, temp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = Truncate8(temp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value)
|
||||||
|
{
|
||||||
|
if ((addr & 1) == 0)
|
||||||
|
{
|
||||||
|
u32 temp = 0;
|
||||||
|
if (!DoSafeMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(addr, temp))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = Truncate16(temp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 low, high;
|
||||||
|
if (!SafeReadMemoryByte(addr, &low) || !SafeReadMemoryByte(addr + 1, &high))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = (ZeroExtend16(high) << 8) | ZeroExtend16(low);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeReadMemoryWord(VirtualMemoryAddress addr, u32* value)
|
||||||
|
{
|
||||||
|
if ((addr & 3) == 0)
|
||||||
|
return DoSafeMemoryAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(addr, *value);
|
||||||
|
|
||||||
|
u16 low, high;
|
||||||
|
if (!SafeReadMemoryHalfWord(addr, &low) || !SafeReadMemoryHalfWord(addr + 2, &high))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = (ZeroExtend32(high) << 16) | ZeroExtend32(low);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeReadMemoryCString(VirtualMemoryAddress addr, std::string* value, u32 max_length /*= 1024*/)
|
||||||
|
{
|
||||||
|
value->clear();
|
||||||
|
|
||||||
|
u8 ch;
|
||||||
|
while (SafeReadMemoryByte(addr, &ch))
|
||||||
|
{
|
||||||
|
if (ch == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
value->push_back(ch);
|
||||||
|
if (value->size() >= max_length)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
addr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
value->clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeWriteMemoryByte(VirtualMemoryAddress addr, u8 value)
|
||||||
|
{
|
||||||
|
u32 temp = ZeroExtend32(value);
|
||||||
|
return DoSafeMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Byte>(addr, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeWriteMemoryHalfWord(VirtualMemoryAddress addr, u16 value)
|
||||||
|
{
|
||||||
|
if ((addr & 1) == 0)
|
||||||
|
{
|
||||||
|
u32 temp = ZeroExtend32(value);
|
||||||
|
return DoSafeMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::HalfWord>(addr, temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SafeWriteMemoryByte(addr, Truncate8(value)) && SafeWriteMemoryByte(addr + 1, Truncate8(value >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::SafeWriteMemoryWord(VirtualMemoryAddress addr, u32 value)
|
||||||
|
{
|
||||||
|
if ((addr & 3) == 0)
|
||||||
|
return DoSafeMemoryAccess<MemoryAccessType::Write, MemoryAccessSize::Word>(addr, value);
|
||||||
|
|
||||||
|
return SafeWriteMemoryHalfWord(addr, Truncate16(value >> 16)) &&
|
||||||
|
SafeWriteMemoryHalfWord(addr + 2, Truncate16(value >> 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
void* CPU::GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks)
|
||||||
|
{
|
||||||
|
using namespace Bus;
|
||||||
|
|
||||||
|
const u32 seg = (address >> 29);
|
||||||
|
if (seg != 0 && seg != 4 && seg != 5)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress paddr = address & PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
if (paddr < RAM_MIRROR_END)
|
||||||
|
{
|
||||||
|
if (read_ticks)
|
||||||
|
*read_ticks = RAM_READ_TICKS;
|
||||||
|
|
||||||
|
return &g_ram[paddr & g_ram_mask];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((paddr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION)
|
||||||
|
{
|
||||||
|
if (read_ticks)
|
||||||
|
*read_ticks = 0;
|
||||||
|
|
||||||
|
return &g_state.dcache[paddr & DCACHE_OFFSET_MASK];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paddr >= BIOS_BASE && paddr < (BIOS_BASE + BIOS_SIZE))
|
||||||
|
{
|
||||||
|
if (read_ticks)
|
||||||
|
*read_ticks = g_bios_access_time[static_cast<u32>(size)];
|
||||||
|
|
||||||
|
return &g_bios[paddr & BIOS_MASK];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* CPU::GetDirectWriteMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size)
|
||||||
|
{
|
||||||
|
using namespace Bus;
|
||||||
|
|
||||||
|
const u32 seg = (address >> 29);
|
||||||
|
if (seg != 0 && seg != 4 && seg != 5)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
const PhysicalMemoryAddress paddr = address & PHYSICAL_MEMORY_ADDRESS_MASK;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// Not enabled until we can protect code regions.
|
||||||
|
if (paddr < RAM_MIRROR_END)
|
||||||
|
return &g_ram[paddr & RAM_MASK];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if ((paddr & DCACHE_LOCATION_MASK) == DCACHE_LOCATION)
|
||||||
|
return &g_state.dcache[paddr & DCACHE_OFFSET_MASK];
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<MemoryAccessType type, MemoryAccessSize size>
|
||||||
|
ALWAYS_INLINE_RELEASE bool CPU::DoAlignmentCheck(VirtualMemoryAddress address)
|
||||||
|
{
|
||||||
|
if constexpr (size == MemoryAccessSize::HalfWord)
|
||||||
|
{
|
||||||
|
if (Common::IsAlignedPow2(address, 2))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if constexpr (size == MemoryAccessSize::Word)
|
||||||
|
{
|
||||||
|
if (Common::IsAlignedPow2(address, 4))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_state.cop0_regs.BadVaddr = address;
|
||||||
|
RaiseException(type == MemoryAccessType::Read ? Exception::AdEL : Exception::AdES);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static void MemoryBreakpoint(MemoryAccessType type, MemoryAccessSize size, VirtualMemoryAddress addr, u32 value)
|
||||||
|
{
|
||||||
|
static constexpr const char* sizes[3] = { "byte", "halfword", "word" };
|
||||||
|
static constexpr const char* types[2] = { "read", "write" };
|
||||||
|
|
||||||
|
const u32 cycle = TimingEvents::GetGlobalTickCounter() + CPU::g_state.pending_ticks;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static std::FILE* fp = nullptr;
|
||||||
|
if (!fp)
|
||||||
|
fp = std::fopen("D:\\memory.txt", "wb");
|
||||||
|
if (fp)
|
||||||
|
{
|
||||||
|
std::fprintf(fp, "%u %s %s %08X %08X\n", cycle, types[static_cast<u32>(type)], sizes[static_cast<u32>(size)], addr, value);
|
||||||
|
std::fflush(fp);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (type == MemoryAccessType::Read && addr == 0x1F000084)
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
#if 0
|
||||||
|
if (type == MemoryAccessType::Write && addr == 0x000000B0 /*&& value == 0x3C080000*/)
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0 // TODO: MEMBP
|
||||||
|
if (type == MemoryAccessType::Write && address == 0x80113028)
|
||||||
|
{
|
||||||
|
if ((TimingEvents::GetGlobalTickCounter() + CPU::g_state.pending_ticks) == 5051485)
|
||||||
|
__debugbreak();
|
||||||
|
|
||||||
|
Log_WarningPrintf("VAL %08X @ %u", value, (TimingEvents::GetGlobalTickCounter() + CPU::g_state.pending_ticks));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#define MEMORY_BREAKPOINT(type, size, addr, value) MemoryBreakpoint((type), (size), (addr), (value))
|
||||||
|
#else
|
||||||
|
#define MEMORY_BREAKPOINT(type, size, addr, value)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool CPU::ReadMemoryByte(VirtualMemoryAddress addr, u8* value)
|
||||||
|
{
|
||||||
|
*value = Truncate8(GetMemoryReadHandler(addr, MemoryAccessSize::Byte)(addr));
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
RaiseException(Exception::DBE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::Byte, addr, *value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value)
|
||||||
|
{
|
||||||
|
if (!DoAlignmentCheck<MemoryAccessType::Read, MemoryAccessSize::HalfWord>(addr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = Truncate16(GetMemoryReadHandler(addr, MemoryAccessSize::HalfWord)(addr));
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
RaiseException(Exception::DBE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::HalfWord, addr, *value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::ReadMemoryWord(VirtualMemoryAddress addr, u32* value)
|
||||||
|
{
|
||||||
|
if (!DoAlignmentCheck<MemoryAccessType::Read, MemoryAccessSize::Word>(addr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
*value = GetMemoryReadHandler(addr, MemoryAccessSize::Word)(addr);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
RaiseException(Exception::DBE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::Word, addr, *value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::WriteMemoryByte(VirtualMemoryAddress addr, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::Byte, addr, value);
|
||||||
|
|
||||||
|
GetMemoryWriteHandler(addr, MemoryAccessSize::Byte)(addr, value);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
RaiseException(Exception::DBE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::WriteMemoryHalfWord(VirtualMemoryAddress addr, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::HalfWord, addr, value);
|
||||||
|
|
||||||
|
if (!DoAlignmentCheck<MemoryAccessType::Write, MemoryAccessSize::HalfWord>(addr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GetMemoryWriteHandler(addr, MemoryAccessSize::HalfWord)(addr, value);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
RaiseException(Exception::DBE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPU::WriteMemoryWord(VirtualMemoryAddress addr, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::Word, addr, value);
|
||||||
|
|
||||||
|
if (!DoAlignmentCheck<MemoryAccessType::Write, MemoryAccessSize::Word>(addr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GetMemoryWriteHandler(addr, MemoryAccessSize::Word)(addr, value);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
RaiseException(Exception::DBE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPU::Recompiler::Thunks::ReadMemoryByte(u32 address)
|
||||||
|
{
|
||||||
|
const u32 value = GetMemoryReadHandler(address, MemoryAccessSize::Byte)(address);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::Byte, address, value);
|
||||||
|
return ZeroExtend64(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPU::Recompiler::Thunks::ReadMemoryHalfWord(u32 address)
|
||||||
|
{
|
||||||
|
if (!Common::IsAlignedPow2(address, 2)) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.cop0_regs.BadVaddr = address;
|
||||||
|
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 value = GetMemoryReadHandler(address, MemoryAccessSize::HalfWord)(address);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::HalfWord, address, value);
|
||||||
|
return ZeroExtend64(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 CPU::Recompiler::Thunks::ReadMemoryWord(u32 address)
|
||||||
|
{
|
||||||
|
if (!Common::IsAlignedPow2(address, 4)) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.cop0_regs.BadVaddr = address;
|
||||||
|
return static_cast<u64>(-static_cast<s64>(Exception::AdEL));
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 value = GetMemoryReadHandler(address, MemoryAccessSize::Word)(address);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
return static_cast<u64>(-static_cast<s64>(Exception::DBE));
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::Word, address, value);
|
||||||
|
return ZeroExtend64(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CPU::Recompiler::Thunks::WriteMemoryByte(u32 address, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::Byte, address, value);
|
||||||
|
|
||||||
|
GetMemoryWriteHandler(address, MemoryAccessSize::Byte)(address, value);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
return static_cast<u32>(Exception::DBE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CPU::Recompiler::Thunks::WriteMemoryHalfWord(u32 address, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::HalfWord, address, value);
|
||||||
|
|
||||||
|
if (!Common::IsAlignedPow2(address, 2)) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.cop0_regs.BadVaddr = address;
|
||||||
|
return static_cast<u32>(Exception::AdES);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetMemoryWriteHandler(address, MemoryAccessSize::HalfWord)(address, value);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
return static_cast<u32>(Exception::DBE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CPU::Recompiler::Thunks::WriteMemoryWord(u32 address, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::Word, address, value);
|
||||||
|
|
||||||
|
if (!Common::IsAlignedPow2(address, 4)) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.cop0_regs.BadVaddr = address;
|
||||||
|
return static_cast<u32>(Exception::AdES);
|
||||||
|
}
|
||||||
|
|
||||||
|
GetMemoryWriteHandler(address, MemoryAccessSize::Word)(address, value);
|
||||||
|
if (g_state.bus_error) [[unlikely]]
|
||||||
|
{
|
||||||
|
g_state.bus_error = false;
|
||||||
|
return static_cast<u32>(Exception::DBE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CPU::Recompiler::Thunks::UncheckedReadMemoryByte(u32 address)
|
||||||
|
{
|
||||||
|
const u32 value = GetMemoryReadHandler(address, MemoryAccessSize::Byte)(address);
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::Byte, address, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CPU::Recompiler::Thunks::UncheckedReadMemoryHalfWord(u32 address)
|
||||||
|
{
|
||||||
|
const u32 value = GetMemoryReadHandler(address, MemoryAccessSize::HalfWord)(address);
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::HalfWord, address, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 CPU::Recompiler::Thunks::UncheckedReadMemoryWord(u32 address)
|
||||||
|
{
|
||||||
|
const u32 value = GetMemoryReadHandler(address, MemoryAccessSize::Word)(address);
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Read, MemoryAccessSize::Word, address, value);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::Recompiler::Thunks::UncheckedWriteMemoryByte(u32 address, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::Byte, address, value);
|
||||||
|
GetMemoryWriteHandler(address, MemoryAccessSize::Byte)(address, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::Recompiler::Thunks::UncheckedWriteMemoryHalfWord(u32 address, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::HalfWord, address, value);
|
||||||
|
GetMemoryWriteHandler(address, MemoryAccessSize::HalfWord)(address, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPU::Recompiler::Thunks::UncheckedWriteMemoryWord(u32 address, u32 value)
|
||||||
|
{
|
||||||
|
MEMORY_BREAKPOINT(MemoryAccessType::Write, MemoryAccessSize::Word, address, value);
|
||||||
|
GetMemoryWriteHandler(address, MemoryAccessSize::Word)(address, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef MEMORY_BREAKPOINT
|
||||||
|
|
|
@ -68,6 +68,7 @@ struct State
|
||||||
bool next_instruction_is_branch_delay_slot = false;
|
bool next_instruction_is_branch_delay_slot = false;
|
||||||
bool branch_was_taken = false;
|
bool branch_was_taken = false;
|
||||||
bool exception_raised = false;
|
bool exception_raised = false;
|
||||||
|
bool bus_error = false;
|
||||||
|
|
||||||
// load delays
|
// load delays
|
||||||
Reg load_delay_reg = Reg::count;
|
Reg load_delay_reg = Reg::count;
|
||||||
|
@ -84,7 +85,8 @@ struct State
|
||||||
// 4 bytes of padding here on x64
|
// 4 bytes of padding here on x64
|
||||||
bool use_debug_dispatcher = false;
|
bool use_debug_dispatcher = false;
|
||||||
|
|
||||||
u8* fastmem_base = nullptr;
|
void* fastmem_base = nullptr;
|
||||||
|
void** memory_handlers = nullptr;
|
||||||
|
|
||||||
// data cache (used as scratchpad)
|
// data cache (used as scratchpad)
|
||||||
std::array<u8, DCACHE_SIZE> dcache = {};
|
std::array<u8, DCACHE_SIZE> dcache = {};
|
||||||
|
@ -102,7 +104,8 @@ void Shutdown();
|
||||||
void Reset();
|
void Reset();
|
||||||
bool DoState(StateWrapper& sw);
|
bool DoState(StateWrapper& sw);
|
||||||
void ClearICache();
|
void ClearICache();
|
||||||
void UpdateFastmemBase();
|
void UpdateMemoryPointers();
|
||||||
|
void ExecutionModeChanged();
|
||||||
|
|
||||||
/// Executes interpreter loop.
|
/// Executes interpreter loop.
|
||||||
void Execute();
|
void Execute();
|
||||||
|
|
|
@ -102,16 +102,11 @@ ALWAYS_INLINE static VirtualMemoryAddress PhysicalAddressToVirtual(PhysicalMemor
|
||||||
return bases[static_cast<u32>(segment)] | address;
|
return bases[static_cast<u32>(segment)] | address;
|
||||||
}
|
}
|
||||||
|
|
||||||
// defined in bus.cpp - memory access functions which return false if an exception was thrown.
|
Bus::MemoryReadHandler GetMemoryReadHandler(VirtualMemoryAddress address, MemoryAccessSize size);
|
||||||
bool FetchInstruction();
|
Bus::MemoryWriteHandler GetMemoryWriteHandler(VirtualMemoryAddress address, MemoryAccessSize size);
|
||||||
bool FetchInstructionForInterpreterFallback();
|
|
||||||
|
// memory access functions which return false if an exception was thrown.
|
||||||
bool SafeReadInstruction(VirtualMemoryAddress addr, u32* value);
|
bool SafeReadInstruction(VirtualMemoryAddress addr, u32* value);
|
||||||
bool ReadMemoryByte(VirtualMemoryAddress addr, u8* value);
|
|
||||||
bool ReadMemoryHalfWord(VirtualMemoryAddress addr, u16* value);
|
|
||||||
bool ReadMemoryWord(VirtualMemoryAddress addr, u32* value);
|
|
||||||
bool WriteMemoryByte(VirtualMemoryAddress addr, u32 value);
|
|
||||||
bool WriteMemoryHalfWord(VirtualMemoryAddress addr, u32 value);
|
|
||||||
bool WriteMemoryWord(VirtualMemoryAddress addr, u32 value);
|
|
||||||
void* GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks);
|
void* GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks);
|
||||||
void* GetDirectWriteMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size);
|
void* GetDirectWriteMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size);
|
||||||
|
|
||||||
|
|
|
@ -2687,18 +2687,18 @@ bool CodeGenerator::Compile_cop0(const CodeBlockInstruction& cbi)
|
||||||
m_speculative_constants.cop0_sr = SpeculativeReadReg(cbi.instruction.r.rt);
|
m_speculative_constants.cop0_sr = SpeculativeReadReg(cbi.instruction.r.rt);
|
||||||
|
|
||||||
// changing SR[Isc] needs to update fastmem views
|
// changing SR[Isc] needs to update fastmem views
|
||||||
if (reg == Cop0Reg::SR && g_settings.IsUsingFastmem())
|
if (reg == Cop0Reg::SR)
|
||||||
{
|
{
|
||||||
LabelType skip_fastmem_update;
|
LabelType skip_mem_update;
|
||||||
Value old_value = m_register_cache.AllocateScratch(RegSize_32);
|
Value old_value = m_register_cache.AllocateScratch(RegSize_32);
|
||||||
EmitLoadCPUStructField(old_value.host_reg, RegSize_32, offset);
|
EmitLoadCPUStructField(old_value.host_reg, RegSize_32, offset);
|
||||||
EmitStoreCPUStructField(offset, value);
|
EmitStoreCPUStructField(offset, value);
|
||||||
EmitXor(old_value.host_reg, old_value.host_reg, value);
|
EmitXor(old_value.host_reg, old_value.host_reg, value);
|
||||||
EmitBranchIfBitClear(old_value.host_reg, RegSize_32, 16, &skip_fastmem_update);
|
EmitBranchIfBitClear(old_value.host_reg, RegSize_32, 16, &skip_mem_update);
|
||||||
m_register_cache.InhibitAllocation();
|
m_register_cache.InhibitAllocation();
|
||||||
EmitFunctionCall(nullptr, &UpdateFastmemBase, m_register_cache.GetCPUPtr());
|
EmitFunctionCall(nullptr, &UpdateMemoryPointers, m_register_cache.GetCPUPtr());
|
||||||
EmitUpdateFastmemBase();
|
EmitUpdateFastmemBase();
|
||||||
EmitBindLabel(&skip_fastmem_update);
|
EmitBindLabel(&skip_mem_update);
|
||||||
m_register_cache.UninhibitAllocation();
|
m_register_cache.UninhibitAllocation();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
|
||||||
static constexpr u32 SAVE_STATE_VERSION = 60;
|
static constexpr u32 SAVE_STATE_VERSION = 61;
|
||||||
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
|
||||||
|
|
||||||
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);
|
||||||
|
|
|
@ -3510,6 +3510,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
TRANSLATE_SV("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName(
|
TRANSLATE_SV("CPUExecutionMode", Settings::GetCPUExecutionModeDisplayName(
|
||||||
g_settings.cpu_execution_mode))),
|
g_settings.cpu_execution_mode))),
|
||||||
5.0f);
|
5.0f);
|
||||||
|
CPU::ExecutionModeChanged();
|
||||||
CPU::CodeCache::Reinitialize();
|
CPU::CodeCache::Reinitialize();
|
||||||
CPU::ClearICache();
|
CPU::ClearICache();
|
||||||
}
|
}
|
||||||
|
@ -3521,6 +3522,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
g_settings.bios_tty_logging != old_settings.bios_tty_logging))
|
g_settings.bios_tty_logging != old_settings.bios_tty_logging))
|
||||||
{
|
{
|
||||||
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Recompiler options changed, flushing all blocks."), 5.0f);
|
Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Recompiler options changed, flushing all blocks."), 5.0f);
|
||||||
|
CPU::ExecutionModeChanged();
|
||||||
|
|
||||||
// changing memory exceptions can re-enable fastmem
|
// changing memory exceptions can re-enable fastmem
|
||||||
if (g_settings.cpu_recompiler_memory_exceptions != old_settings.cpu_recompiler_memory_exceptions)
|
if (g_settings.cpu_recompiler_memory_exceptions != old_settings.cpu_recompiler_memory_exceptions)
|
||||||
|
|
Loading…
Reference in a new issue