mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 07:35:41 +00:00
CPU: Refactor load delay handling
Now works when mixing interpreter and recompiler code.
This commit is contained in:
parent
1d6c4a3af1
commit
5217088d82
|
@ -4,6 +4,9 @@
|
|||
#include "cpu_disasm.h"
|
||||
#include "cpu_recompiler_code_generator.h"
|
||||
#include "cpu_recompiler_thunks.h"
|
||||
#include "system.h"
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
Log_SetChannel(CPU::CodeCache);
|
||||
|
||||
namespace CPU {
|
||||
|
@ -15,8 +18,9 @@ CodeCache::CodeCache() = default;
|
|||
|
||||
CodeCache::~CodeCache() = default;
|
||||
|
||||
void CodeCache::Initialize(Core* core, Bus* bus)
|
||||
void CodeCache::Initialize(System* system, Core* core, Bus* bus)
|
||||
{
|
||||
m_system = system;
|
||||
m_core = core;
|
||||
m_bus = bus;
|
||||
|
||||
|
@ -122,9 +126,12 @@ bool CodeCache::CompileBlock(CodeBlock* block)
|
|||
}
|
||||
|
||||
cbi.pc = pc;
|
||||
cbi.is_branch = IsBranchInstruction(cbi.instruction);
|
||||
cbi.is_branch_delay_slot = is_branch_delay_slot;
|
||||
cbi.is_load_delay_slot = is_load_delay_slot;
|
||||
cbi.is_branch_instruction = IsBranchInstruction(cbi.instruction);
|
||||
cbi.is_load_instruction = IsMemoryLoadInstruction(cbi.instruction);
|
||||
cbi.is_store_instruction = IsMemoryStoreInstruction(cbi.instruction);
|
||||
cbi.has_load_delay = InstructionHasLoadDelay(cbi.instruction);
|
||||
cbi.can_trap = CanInstructionTrap(cbi.instruction, m_core->InUserMode());
|
||||
|
||||
// instruction is decoded now
|
||||
|
@ -133,11 +140,14 @@ bool CodeCache::CompileBlock(CodeBlock* block)
|
|||
|
||||
// if we're in a branch delay slot, the block is now done
|
||||
// except if this is a branch in a branch delay slot, then we grab the one after that, and so on...
|
||||
if (is_branch_delay_slot && !cbi.is_branch)
|
||||
if (is_branch_delay_slot && !cbi.is_branch_instruction)
|
||||
break;
|
||||
|
||||
// if this is a branch, we grab the next instruction (delay slot), and then exit
|
||||
is_branch_delay_slot = cbi.is_branch;
|
||||
is_branch_delay_slot = cbi.is_branch_instruction;
|
||||
|
||||
// same for load delay
|
||||
is_load_delay_slot = cbi.has_load_delay;
|
||||
|
||||
// is this a non-branchy exit? (e.g. syscall)
|
||||
if (IsExitBlockInstruction(cbi.instruction))
|
||||
|
@ -254,10 +264,7 @@ void CodeCache::InterpretCachedBlock(const CodeBlock& block)
|
|||
m_core->ExecuteInstruction();
|
||||
|
||||
// next load delay
|
||||
m_core->m_load_delay_reg = m_core->m_next_load_delay_reg;
|
||||
m_core->m_next_load_delay_reg = Reg::count;
|
||||
m_core->m_load_delay_old_value = m_core->m_next_load_delay_old_value;
|
||||
m_core->m_next_load_delay_old_value = 0;
|
||||
m_core->UpdateLoadDelay();
|
||||
|
||||
if (m_core->m_exception_raised)
|
||||
break;
|
||||
|
@ -294,10 +301,7 @@ void CodeCache::InterpretUncachedBlock()
|
|||
m_core->ExecuteInstruction();
|
||||
|
||||
// next load delay
|
||||
m_core->m_load_delay_reg = m_core->m_next_load_delay_reg;
|
||||
m_core->m_next_load_delay_reg = Reg::count;
|
||||
m_core->m_load_delay_old_value = m_core->m_next_load_delay_old_value;
|
||||
m_core->m_next_load_delay_old_value = 0;
|
||||
m_core->UpdateLoadDelay();
|
||||
|
||||
const bool branch = IsBranchInstruction(m_core->m_current_instruction);
|
||||
if (m_core->m_exception_raised || (!branch && in_branch_delay_slot) ||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
class JitCodeBuffer;
|
||||
|
||||
class Bus;
|
||||
class System;
|
||||
|
||||
namespace CPU {
|
||||
class Core;
|
||||
|
@ -23,7 +24,7 @@ public:
|
|||
CodeCache();
|
||||
~CodeCache();
|
||||
|
||||
void Initialize(Core* core, Bus* bus);
|
||||
void Initialize(System* system, Core* core, Bus* bus);
|
||||
void Reset();
|
||||
void Execute();
|
||||
|
||||
|
@ -39,6 +40,7 @@ private:
|
|||
void InterpretCachedBlock(const CodeBlock& block);
|
||||
void InterpretUncachedBlock();
|
||||
|
||||
System* m_system;
|
||||
Core* m_core;
|
||||
Bus* m_bus;
|
||||
|
||||
|
|
|
@ -97,8 +97,10 @@ bool Core::DoState(StateWrapper& sw)
|
|||
sw.Do(&m_next_instruction_is_branch_delay_slot);
|
||||
sw.Do(&m_branch_was_taken);
|
||||
sw.Do(&m_load_delay_reg);
|
||||
sw.Do(&m_load_delay_value);
|
||||
sw.Do(&m_load_delay_old_value);
|
||||
sw.Do(&m_next_load_delay_reg);
|
||||
sw.Do(&m_next_load_delay_value);
|
||||
sw.Do(&m_next_load_delay_old_value);
|
||||
sw.Do(&m_cache_control);
|
||||
sw.DoBytes(m_dcache.data(), m_dcache.size());
|
||||
|
@ -354,18 +356,15 @@ void Core::DispatchInterrupt()
|
|||
m_next_instruction.cop.cop_n);
|
||||
}
|
||||
|
||||
void Core::FlushLoadDelay()
|
||||
{
|
||||
m_load_delay_reg = Reg::count;
|
||||
m_load_delay_old_value = 0;
|
||||
m_next_load_delay_reg = Reg::count;
|
||||
m_next_load_delay_old_value = 0;
|
||||
}
|
||||
|
||||
void Core::FlushPipeline()
|
||||
{
|
||||
// loads are flushed
|
||||
FlushLoadDelay();
|
||||
m_next_load_delay_reg = Reg::count;
|
||||
if (m_load_delay_reg != Reg::count)
|
||||
{
|
||||
m_regs.r[static_cast<u8>(m_load_delay_reg)] = m_load_delay_value;
|
||||
m_load_delay_reg = Reg::count;
|
||||
}
|
||||
|
||||
// not in a branch delay slot
|
||||
m_branch_was_taken = false;
|
||||
|
@ -383,13 +382,15 @@ void Core::FlushPipeline()
|
|||
|
||||
u32 Core::ReadReg(Reg rs)
|
||||
{
|
||||
return rs == m_load_delay_reg ? m_load_delay_old_value : m_regs.r[static_cast<u8>(rs)];
|
||||
return m_regs.r[static_cast<u8>(rs)];
|
||||
}
|
||||
|
||||
void Core::WriteReg(Reg rd, u32 value)
|
||||
{
|
||||
if (rd != Reg::zero)
|
||||
m_regs.r[static_cast<u8>(rd)] = value;
|
||||
|
||||
// prevent writes to $zero from going through - better than branching/cmov
|
||||
m_regs.zero = 0;
|
||||
}
|
||||
|
||||
void Core::WriteRegDelayed(Reg rd, u32 value)
|
||||
|
@ -398,10 +399,14 @@ void Core::WriteRegDelayed(Reg rd, u32 value)
|
|||
if (rd == Reg::zero)
|
||||
return;
|
||||
|
||||
// save the old value, this will be returned if the register is read in the next instruction
|
||||
// double load delays ignore the first value
|
||||
if (m_load_delay_reg == rd)
|
||||
m_load_delay_reg = Reg::count;
|
||||
|
||||
// save the old value, if something else overwrites this reg we want to preserve it
|
||||
m_next_load_delay_reg = rd;
|
||||
m_next_load_delay_old_value = ReadReg(rd);
|
||||
m_regs.r[static_cast<u8>(rd)] = value;
|
||||
m_next_load_delay_value = value;
|
||||
m_next_load_delay_old_value = m_regs.r[static_cast<u8>(rd)];
|
||||
}
|
||||
|
||||
std::optional<u32> Core::ReadCop0Reg(Cop0Reg reg)
|
||||
|
@ -608,10 +613,7 @@ void Core::Execute()
|
|||
ExecuteInstruction();
|
||||
|
||||
// next load delay
|
||||
m_load_delay_reg = m_next_load_delay_reg;
|
||||
m_next_load_delay_reg = Reg::count;
|
||||
m_load_delay_old_value = m_next_load_delay_old_value;
|
||||
m_next_load_delay_old_value = 0;
|
||||
UpdateLoadDelay();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1039,8 +1041,8 @@ void Core::ExecuteInstruction()
|
|||
if (!ReadMemoryWord(aligned_addr, &aligned_value))
|
||||
return;
|
||||
|
||||
// note: bypasses load delay on the read
|
||||
const u32 existing_value = m_regs.r[static_cast<u8>(inst.i.rt.GetValue())];
|
||||
// Bypasses load delay. No need to check the old value since this is the delay slot or it's not relevant.
|
||||
const u32 existing_value = (inst.i.rt == m_load_delay_reg) ? m_load_delay_value : ReadReg(inst.i.rt);
|
||||
const u8 shift = (Truncate8(addr) & u8(3)) * u8(8);
|
||||
u32 new_value;
|
||||
if (inst.op == InstructionOp::lwl)
|
||||
|
|
|
@ -14,11 +14,10 @@ namespace CPU {
|
|||
|
||||
class CodeCache;
|
||||
|
||||
namespace Recompiler
|
||||
{
|
||||
namespace Recompiler {
|
||||
class CodeGenerator;
|
||||
class Thunks;
|
||||
}
|
||||
} // namespace Recompiler
|
||||
|
||||
class Core
|
||||
{
|
||||
|
@ -103,6 +102,19 @@ private:
|
|||
void DisassembleAndLog(u32 addr);
|
||||
void DisassembleAndPrint(u32 addr, u32 instructions_before, u32 instructions_after);
|
||||
|
||||
// Updates load delays - call after each instruction
|
||||
ALWAYS_INLINE void UpdateLoadDelay()
|
||||
{
|
||||
// the old value is needed in case the delay slot instruction overwrites the same register
|
||||
if (m_load_delay_reg != Reg::count && m_regs.r[static_cast<u8>(m_load_delay_reg)] == m_load_delay_old_value)
|
||||
m_regs.r[static_cast<u8>(m_load_delay_reg)] = m_load_delay_value;
|
||||
|
||||
m_load_delay_reg = m_next_load_delay_reg;
|
||||
m_load_delay_value = m_next_load_delay_value;
|
||||
m_load_delay_old_value = m_next_load_delay_old_value;
|
||||
m_next_load_delay_reg = Reg::count;
|
||||
}
|
||||
|
||||
// Fetches the instruction at m_regs.npc
|
||||
bool FetchInstruction();
|
||||
void ExecuteInstruction();
|
||||
|
@ -117,9 +129,6 @@ private:
|
|||
bool HasPendingInterrupt();
|
||||
void DispatchInterrupt();
|
||||
|
||||
// flushes any load delays if present
|
||||
void FlushLoadDelay();
|
||||
|
||||
// clears pipeline of load/branch delays
|
||||
void FlushPipeline();
|
||||
|
||||
|
@ -158,8 +167,10 @@ private:
|
|||
|
||||
// load delays
|
||||
Reg m_load_delay_reg = Reg::count;
|
||||
u32 m_load_delay_value = 0;
|
||||
u32 m_load_delay_old_value = 0;
|
||||
Reg m_next_load_delay_reg = Reg::count;
|
||||
u32 m_next_load_delay_value = 0;
|
||||
u32 m_next_load_delay_old_value = 0;
|
||||
|
||||
u32 m_cache_control = 0;
|
||||
|
|
|
@ -28,11 +28,6 @@ bool CodeGenerator::CompileBlock(const CodeBlock* block, CodeBlock::HostCodePoin
|
|||
m_block_start = block->instructions.data();
|
||||
m_block_end = block->instructions.data() + block->instructions.size();
|
||||
|
||||
m_current_instruction_in_branch_delay_slot_dirty = true;
|
||||
m_branch_was_taken_dirty = true;
|
||||
m_current_instruction_was_branch_taken_dirty = false;
|
||||
m_load_delay_dirty = true;
|
||||
|
||||
EmitBeginBlock();
|
||||
BlockPrologue();
|
||||
|
||||
|
@ -340,7 +335,11 @@ void CodeGenerator::BlockPrologue()
|
|||
{
|
||||
EmitStoreCPUStructField(offsetof(Core, m_exception_raised), Value::FromConstantU8(0));
|
||||
|
||||
// fetching of the first instruction...
|
||||
// we don't know the state of the last block, so assume load delays might be in progress
|
||||
m_current_instruction_in_branch_delay_slot_dirty = true;
|
||||
m_branch_was_taken_dirty = true;
|
||||
m_current_instruction_was_branch_taken_dirty = false;
|
||||
m_load_delay_dirty = true;
|
||||
|
||||
// sync m_current_instruction_pc so we can simply add to it
|
||||
SyncCurrentInstructionPC();
|
||||
|
@ -435,7 +434,7 @@ void CodeGenerator::InstructionPrologue(const CodeBlockInstruction& cbi, TickCou
|
|||
m_delayed_pc_add = 0;
|
||||
}
|
||||
|
||||
if (!cbi.is_branch)
|
||||
if (!cbi.is_branch_instruction)
|
||||
m_delayed_pc_add = INSTRUCTION_SIZE;
|
||||
|
||||
m_delayed_cycles_add += cycles;
|
||||
|
@ -445,38 +444,18 @@ void CodeGenerator::InstructionPrologue(const CodeBlockInstruction& cbi, TickCou
|
|||
void CodeGenerator::InstructionEpilogue(const CodeBlockInstruction& cbi)
|
||||
{
|
||||
// copy if the previous instruction was a load, reset the current value on the next instruction
|
||||
if (m_load_delay_dirty)
|
||||
if (m_next_load_delay_dirty)
|
||||
{
|
||||
// cpu->m_load_delay_reg = cpu->m_next_load_delay_reg;
|
||||
// cpu->m_next_load_delay_reg = Reg::count;
|
||||
{
|
||||
Value temp = m_register_cache.AllocateScratch(RegSize_8);
|
||||
EmitLoadCPUStructField(temp.host_reg, RegSize_8, offsetof(Core, m_next_load_delay_reg));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_next_load_delay_reg),
|
||||
Value::FromConstantU8(static_cast<u8>(Reg::count)));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_load_delay_reg), temp);
|
||||
}
|
||||
|
||||
// cpu->m_load_delay_old_value = cpu->m_next_load_delay_old_value;
|
||||
// cpu->m_next_load_delay_old_value = 0;
|
||||
{
|
||||
Value temp = m_register_cache.AllocateScratch(RegSize_32);
|
||||
EmitLoadCPUStructField(temp.host_reg, RegSize_32, offsetof(Core, m_next_load_delay_old_value));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_next_load_delay_old_value), Value::FromConstantU32(0));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_load_delay_old_value), temp);
|
||||
}
|
||||
|
||||
m_load_delay_dirty = false;
|
||||
m_next_load_delay_dirty = true;
|
||||
}
|
||||
else if (m_next_load_delay_dirty)
|
||||
{
|
||||
// cpu->m_load_delay_reg = Reg::count;
|
||||
// cpu->m_load_delay_old_value = 0;
|
||||
EmitStoreCPUStructField(offsetof(Core, m_load_delay_reg), Value::FromConstantU8(static_cast<u8>(Reg::count)));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_load_delay_old_value), Value::FromConstantU32(0));
|
||||
|
||||
Log_DebugPrint("Emitting delay slot flush (with move next)");
|
||||
EmitDelaySlotUpdate(false, false, true);
|
||||
m_next_load_delay_dirty = false;
|
||||
m_load_delay_dirty = true;
|
||||
}
|
||||
else if (m_load_delay_dirty)
|
||||
{
|
||||
Log_DebugPrint("Emitting delay slot flush");
|
||||
EmitDelaySlotUpdate(true, false, false);
|
||||
m_load_delay_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -528,9 +507,9 @@ bool CodeGenerator::Compile_Fallback(const CodeBlockInstruction& cbi)
|
|||
EmitFunctionCall(nullptr, &Thunks::InterpretInstruction, m_register_cache.GetCPUPtr());
|
||||
}
|
||||
|
||||
m_current_instruction_in_branch_delay_slot_dirty = cbi.is_branch;
|
||||
m_branch_was_taken_dirty = cbi.is_branch;
|
||||
m_load_delay_dirty = true;
|
||||
m_current_instruction_in_branch_delay_slot_dirty = cbi.is_branch_instruction;
|
||||
m_branch_was_taken_dirty = cbi.is_branch_instruction;
|
||||
m_next_load_delay_dirty = cbi.has_load_delay;
|
||||
InstructionEpilogue(cbi);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -147,6 +147,7 @@ private:
|
|||
void SyncCurrentInstructionPC();
|
||||
void SyncPC();
|
||||
void AddPendingCycles();
|
||||
void EmitDelaySlotUpdate(bool skip_check_for_delay, bool skip_check_old_value, bool move_next);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Instruction Code Generators
|
||||
|
@ -172,14 +173,12 @@ private:
|
|||
|
||||
std::array<Value, 3> m_operand_memory_addresses{};
|
||||
|
||||
Xbyak::Label m_block_exit_label;
|
||||
|
||||
// whether various flags need to be reset.
|
||||
bool m_current_instruction_in_branch_delay_slot_dirty = false;
|
||||
bool m_branch_was_taken_dirty = false;
|
||||
bool m_current_instruction_was_branch_taken_dirty = false;
|
||||
bool m_next_load_delay_dirty = false;
|
||||
bool m_load_delay_dirty = false;
|
||||
bool m_next_load_delay_dirty = false;
|
||||
};
|
||||
|
||||
} // namespace CPU_X86::Recompiler
|
||||
|
|
|
@ -151,9 +151,8 @@ void CodeGenerator::EmitBeginBlock()
|
|||
|
||||
void CodeGenerator::EmitEndBlock()
|
||||
{
|
||||
m_emit.L(m_block_exit_label);
|
||||
m_register_cache.FreeHostReg(RCPUPTR);
|
||||
m_register_cache.PopCalleeSavedRegisters();
|
||||
m_register_cache.PopCalleeSavedRegisters(true);
|
||||
|
||||
m_emit.ret();
|
||||
}
|
||||
|
@ -166,9 +165,10 @@ void CodeGenerator::EmitBlockExitOnBool(const Value& value)
|
|||
m_emit.test(GetHostReg8(value), GetHostReg8(value));
|
||||
m_emit.jz(continue_label);
|
||||
|
||||
// flush current state
|
||||
// flush current state and return
|
||||
m_register_cache.FlushAllGuestRegisters(false, false);
|
||||
m_emit.jmp(m_block_exit_label, Xbyak::CodeGenerator::T_NEAR);
|
||||
m_register_cache.PopCalleeSavedRegisters(false);
|
||||
m_emit.ret();
|
||||
|
||||
m_emit.L(continue_label);
|
||||
}
|
||||
|
@ -1303,6 +1303,62 @@ void CodeGenerator::EmitAddCPUStructField(u32 offset, const Value& value)
|
|||
}
|
||||
}
|
||||
|
||||
void CodeGenerator::EmitDelaySlotUpdate(bool skip_check_for_delay, bool skip_check_old_value, bool move_next)
|
||||
{
|
||||
Value reg = m_register_cache.AllocateScratch(RegSize_8);
|
||||
Value value = m_register_cache.AllocateScratch(RegSize_32);
|
||||
|
||||
Xbyak::Label skip_flush;
|
||||
|
||||
auto load_delay_reg = m_emit.byte[GetCPUPtrReg() + offsetof(Core, m_load_delay_reg)];
|
||||
auto load_delay_old_value = m_emit.dword[GetCPUPtrReg() + offsetof(Core, m_load_delay_old_value)];
|
||||
auto load_delay_value = m_emit.dword[GetCPUPtrReg() + offsetof(Core, m_load_delay_value)];
|
||||
auto reg_ptr = m_emit.dword[GetCPUPtrReg() + offsetof(Core, m_regs.r[0]) + GetHostReg64(reg.host_reg) * 4];
|
||||
|
||||
// reg = load_delay_reg
|
||||
m_emit.movzx(GetHostReg32(reg.host_reg), load_delay_reg);
|
||||
if (!skip_check_old_value)
|
||||
m_emit.mov(GetHostReg32(value), load_delay_old_value);
|
||||
|
||||
if (!skip_check_for_delay)
|
||||
{
|
||||
// if load_delay_reg == Reg::count goto skip_flush
|
||||
m_emit.cmp(GetHostReg32(reg.host_reg), static_cast<u8>(Reg::count));
|
||||
m_emit.je(skip_flush);
|
||||
}
|
||||
|
||||
if (!skip_check_old_value)
|
||||
{
|
||||
// if r[reg] != load_delay_old_value goto skip_flush
|
||||
m_emit.cmp(GetHostReg32(value), reg_ptr);
|
||||
m_emit.jne(skip_flush);
|
||||
}
|
||||
|
||||
// r[reg] = load_delay_value
|
||||
m_emit.mov(GetHostReg32(value), load_delay_value);
|
||||
m_emit.mov(reg_ptr, GetHostReg32(value));
|
||||
|
||||
// if !move_next load_delay_reg = Reg::count
|
||||
if (!move_next)
|
||||
m_emit.mov(load_delay_reg, static_cast<u8>(Reg::count));
|
||||
|
||||
m_emit.L(skip_flush);
|
||||
|
||||
if (move_next)
|
||||
{
|
||||
auto next_load_delay_reg = m_emit.byte[GetCPUPtrReg() + offsetof(Core, m_next_load_delay_reg)];
|
||||
auto next_load_delay_old_value = m_emit.dword[GetCPUPtrReg() + offsetof(Core, m_next_load_delay_old_value)];
|
||||
auto next_load_delay_value = m_emit.dword[GetCPUPtrReg() + offsetof(Core, m_next_load_delay_value)];
|
||||
m_emit.mov(GetHostReg32(value), next_load_delay_value);
|
||||
m_emit.mov(GetHostReg8(reg), next_load_delay_reg);
|
||||
m_emit.mov(load_delay_value, GetHostReg32(value));
|
||||
m_emit.mov(GetHostReg32(value), next_load_delay_old_value);
|
||||
m_emit.mov(load_delay_reg, GetHostReg8(reg));
|
||||
m_emit.mov(load_delay_old_value, GetHostReg32(value));
|
||||
m_emit.mov(next_load_delay_reg, static_cast<u8>(Reg::count));
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
class ThunkGenerator
|
||||
{
|
||||
|
|
|
@ -307,7 +307,7 @@ u32 RegisterCache::PopCallerSavedRegisters() const
|
|||
return count;
|
||||
}
|
||||
|
||||
u32 RegisterCache::PopCalleeSavedRegisters()
|
||||
u32 RegisterCache::PopCalleeSavedRegisters(bool commit)
|
||||
{
|
||||
if (m_host_register_callee_saved_order_count == 0)
|
||||
return 0;
|
||||
|
@ -321,6 +321,7 @@ u32 RegisterCache::PopCalleeSavedRegisters()
|
|||
(HostRegState::CalleeSaved | HostRegState::CalleeSavedAllocated));
|
||||
|
||||
m_code_generator.EmitPopHostReg(reg);
|
||||
if (commit)
|
||||
m_host_register_state[reg] &= ~HostRegState::CalleeSavedAllocated;
|
||||
count++;
|
||||
i--;
|
||||
|
|
|
@ -176,7 +176,7 @@ public:
|
|||
u32 PopCallerSavedRegisters() const;
|
||||
|
||||
/// Restore callee-saved registers. Call at the end of the function.
|
||||
u32 PopCalleeSavedRegisters();
|
||||
u32 PopCalleeSavedRegisters(bool commit);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Scratch Register Allocation
|
||||
|
|
|
@ -40,4 +40,9 @@ bool Thunks::InterpretInstruction(Core* cpu)
|
|||
return cpu->m_exception_raised;
|
||||
}
|
||||
|
||||
void Thunks::UpdateLoadDelay(Core* cpu)
|
||||
{
|
||||
cpu->UpdateLoadDelay();
|
||||
}
|
||||
|
||||
} // namespace CPU::Recompiler
|
|
@ -20,6 +20,7 @@ public:
|
|||
static bool WriteMemoryHalfWord(Core* cpu, u32 address, u16 value);
|
||||
static bool WriteMemoryWord(Core* cpu, u32 address, u32 value);
|
||||
static bool InterpretInstruction(Core* cpu);
|
||||
static void UpdateLoadDelay(Core* cpu);
|
||||
};
|
||||
|
||||
class ASMFunctions
|
||||
|
|
|
@ -44,6 +44,76 @@ bool IsBranchInstruction(const Instruction& instruction)
|
|||
}
|
||||
}
|
||||
|
||||
bool IsMemoryLoadInstruction(const Instruction& instruction)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
case InstructionOp::lb:
|
||||
case InstructionOp::lh:
|
||||
case InstructionOp::lw:
|
||||
case InstructionOp::lbu:
|
||||
case InstructionOp::lhu:
|
||||
case InstructionOp::lwl:
|
||||
case InstructionOp::lwr:
|
||||
return true;
|
||||
|
||||
case InstructionOp::lwc2:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsMemoryStoreInstruction(const Instruction& instruction)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
case InstructionOp::sb:
|
||||
case InstructionOp::sh:
|
||||
case InstructionOp::sw:
|
||||
case InstructionOp::swl:
|
||||
case InstructionOp::swr:
|
||||
return true;
|
||||
|
||||
case InstructionOp::swc2:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool InstructionHasLoadDelay(const Instruction& instruction)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
case InstructionOp::lb:
|
||||
case InstructionOp::lh:
|
||||
case InstructionOp::lw:
|
||||
case InstructionOp::lbu:
|
||||
case InstructionOp::lhu:
|
||||
case InstructionOp::lwl:
|
||||
case InstructionOp::lwr:
|
||||
return true;
|
||||
|
||||
case InstructionOp::cop0:
|
||||
case InstructionOp::cop2:
|
||||
{
|
||||
if (instruction.cop.IsCommonInstruction())
|
||||
{
|
||||
const CopCommonInstruction common_op = instruction.cop.CommonOp();
|
||||
return (common_op == CopCommonInstruction::cfcn || common_op == CopCommonInstruction::mfcn);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsExitBlockInstruction(const Instruction& instruction)
|
||||
{
|
||||
switch (instruction.op)
|
||||
|
@ -167,26 +237,6 @@ bool CanInstructionTrap(const Instruction& instruction, bool in_user_mode)
|
|||
}
|
||||
}
|
||||
|
||||
bool IsLoadDelayingInstruction(const Instruction& instruction)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
case InstructionOp::lb:
|
||||
case InstructionOp::lh:
|
||||
case InstructionOp::lw:
|
||||
case InstructionOp::lbu:
|
||||
case InstructionOp::lhu:
|
||||
return true;
|
||||
|
||||
case InstructionOp::lwl:
|
||||
case InstructionOp::lwr:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsInvalidInstruction(const Instruction& instruction)
|
||||
{
|
||||
// TODO
|
||||
|
|
|
@ -208,9 +208,11 @@ union Instruction
|
|||
|
||||
// Instruction helpers.
|
||||
bool IsBranchInstruction(const Instruction& instruction);
|
||||
bool IsMemoryLoadInstruction(const Instruction& instruction);
|
||||
bool IsMemoryStoreInstruction(const Instruction& instruction);
|
||||
bool InstructionHasLoadDelay(const Instruction& instruction);
|
||||
bool IsExitBlockInstruction(const Instruction& instruction);
|
||||
bool CanInstructionTrap(const Instruction& instruction, bool in_user_mode);
|
||||
bool IsLoadDelayingInstruction(const Instruction& instruction);
|
||||
bool IsInvalidInstruction(const Instruction& instruction);
|
||||
|
||||
struct Registers
|
||||
|
@ -396,10 +398,13 @@ struct CodeBlockInstruction
|
|||
Instruction instruction;
|
||||
u32 pc;
|
||||
|
||||
bool is_branch : 1;
|
||||
bool is_branch_instruction : 1;
|
||||
bool is_branch_delay_slot : 1;
|
||||
bool is_load_instruction : 1;
|
||||
bool is_store_instruction : 1;
|
||||
bool is_load_delay_slot : 1;
|
||||
bool is_last_instruction : 1;
|
||||
bool has_load_delay : 1;
|
||||
bool can_trap : 1;
|
||||
};
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ bool System::Boot(const char* filename)
|
|||
void System::InitializeComponents()
|
||||
{
|
||||
m_cpu->Initialize(m_bus.get());
|
||||
m_cpu_code_cache->Initialize(m_cpu.get(), m_bus.get());
|
||||
m_cpu_code_cache->Initialize(this, m_cpu.get(), m_bus.get());
|
||||
m_bus->Initialize(m_cpu.get(), m_cpu_code_cache.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(),
|
||||
m_cdrom.get(), m_pad.get(), m_timers.get(), m_spu.get(), m_mdec.get());
|
||||
|
||||
|
|
Loading…
Reference in a new issue