mirror of https://github.com/stenzek/duckstation
CPU: Basic recompiler implementation for x64 (lui, ori, addiu)
Disabled by default.pull/22/head
parent
0e8ff85f04
commit
1d6c4a3af1
@ -0,0 +1,313 @@
|
||||
#include "cpu_code_cache.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "cpu_core.h"
|
||||
#include "cpu_disasm.h"
|
||||
#include "cpu_recompiler_code_generator.h"
|
||||
#include "cpu_recompiler_thunks.h"
|
||||
Log_SetChannel(CPU::CodeCache);
|
||||
|
||||
namespace CPU {
|
||||
|
||||
bool USE_CODE_CACHE = false;
|
||||
bool USE_RECOMPILER = false;
|
||||
|
||||
CodeCache::CodeCache() = default;
|
||||
|
||||
CodeCache::~CodeCache() = default;
|
||||
|
||||
void CodeCache::Initialize(Core* core, Bus* bus)
|
||||
{
|
||||
m_core = core;
|
||||
m_bus = bus;
|
||||
|
||||
m_code_buffer = std::make_unique<JitCodeBuffer>();
|
||||
m_asm_functions = std::make_unique<Recompiler::ASMFunctions>();
|
||||
m_asm_functions->Generate(m_code_buffer.get());
|
||||
}
|
||||
|
||||
void CodeCache::Execute()
|
||||
{
|
||||
while (m_core->m_downcount >= 0)
|
||||
{
|
||||
if (m_core->HasPendingInterrupt())
|
||||
{
|
||||
// TODO: Fill in m_next_instruction...
|
||||
m_core->DispatchInterrupt();
|
||||
}
|
||||
|
||||
m_current_block = GetNextBlock();
|
||||
if (!m_current_block)
|
||||
{
|
||||
Log_WarningPrintf("Falling back to uncached interpreter at 0x%08X", m_core->GetRegs().pc);
|
||||
InterpretUncachedBlock();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (USE_RECOMPILER)
|
||||
m_current_block->host_code(m_core);
|
||||
else
|
||||
InterpretCachedBlock(*m_current_block);
|
||||
|
||||
if (m_current_block_flushed)
|
||||
{
|
||||
m_current_block_flushed = false;
|
||||
delete m_current_block;
|
||||
}
|
||||
|
||||
m_current_block = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::Reset()
|
||||
{
|
||||
m_bus->ClearRAMCodePageFlags();
|
||||
for (auto& it : m_ram_block_map)
|
||||
it.clear();
|
||||
|
||||
m_blocks.clear();
|
||||
m_code_buffer->Reset();
|
||||
}
|
||||
|
||||
const CPU::CodeBlock* CodeCache::GetNextBlock()
|
||||
{
|
||||
const u32 address = m_bus->UnmirrorAddress(m_core->m_regs.pc & UINT32_C(0x1FFFFFFF));
|
||||
|
||||
CodeBlockKey key = {};
|
||||
key.SetPC(address);
|
||||
key.user_mode = m_core->InUserMode();
|
||||
|
||||
BlockMap::iterator iter = m_blocks.find(key.bits);
|
||||
if (iter != m_blocks.end())
|
||||
return iter->second;
|
||||
|
||||
CodeBlock* block = new CodeBlock();
|
||||
block->key = key;
|
||||
if (CompileBlock(block))
|
||||
{
|
||||
// insert into the page map
|
||||
if (m_bus->IsRAMAddress(address))
|
||||
{
|
||||
const u32 start_page = block->GetStartPageIndex();
|
||||
const u32 end_page = block->GetEndPageIndex();
|
||||
for (u32 page = start_page; page < end_page; page++)
|
||||
{
|
||||
m_ram_block_map[page].push_back(block);
|
||||
m_bus->SetRAMCodePage(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_ErrorPrintf("Failed to compile block at PC=0x%08X", address);
|
||||
}
|
||||
|
||||
iter = m_blocks.emplace(key.bits, block).first;
|
||||
return block;
|
||||
}
|
||||
|
||||
bool CodeCache::CompileBlock(CodeBlock* block)
|
||||
{
|
||||
u32 pc = block->GetPC();
|
||||
bool is_branch_delay_slot = false;
|
||||
bool is_load_delay_slot = false;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
CodeBlockInstruction cbi = {};
|
||||
if (!m_bus->IsCacheableAddress(pc) ||
|
||||
m_bus->DispatchAccess<MemoryAccessType::Read, MemoryAccessSize::Word>(pc, cbi.instruction.bits) < 0 ||
|
||||
!IsInvalidInstruction(cbi.instruction))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
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.can_trap = CanInstructionTrap(cbi.instruction, m_core->InUserMode());
|
||||
|
||||
// instruction is decoded now
|
||||
block->instructions.push_back(cbi);
|
||||
pc += sizeof(cbi.instruction.bits);
|
||||
|
||||
// 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)
|
||||
break;
|
||||
|
||||
// if this is a branch, we grab the next instruction (delay slot), and then exit
|
||||
is_branch_delay_slot = cbi.is_branch;
|
||||
|
||||
// is this a non-branchy exit? (e.g. syscall)
|
||||
if (IsExitBlockInstruction(cbi.instruction))
|
||||
break;
|
||||
}
|
||||
|
||||
if (!block->instructions.empty())
|
||||
{
|
||||
block->instructions.back().is_last_instruction = true;
|
||||
|
||||
#ifdef _DEBUG
|
||||
SmallString disasm;
|
||||
Log_DebugPrintf("Block at 0x%08X", block->GetPC());
|
||||
for (const CodeBlockInstruction& cbi : block->instructions)
|
||||
{
|
||||
CPU::DisassembleInstruction(&disasm, cbi.pc, cbi.instruction.bits, nullptr);
|
||||
Log_DebugPrintf("[%s %s 0x%08X] %08X %s", cbi.is_branch_delay_slot ? "BD" : " ",
|
||||
cbi.is_load_delay_slot ? "LD" : " ", cbi.pc, cbi.instruction.bits, disasm.GetCharArray());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
Log_WarningPrintf("Empty block compiled at 0x%08X", block->key.GetPC());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (USE_RECOMPILER)
|
||||
{
|
||||
// Ensure we're not going to run out of space while compiling this block.
|
||||
if (m_code_buffer->GetFreeCodeSpace() < (block->instructions.size() * Recompiler::MAX_HOST_BYTES_PER_INSTRUCTION))
|
||||
{
|
||||
Log_WarningPrintf("Out of code space, flushing all blocks.");
|
||||
Reset();
|
||||
}
|
||||
|
||||
Recompiler::CodeGenerator codegen(m_core, m_code_buffer.get(), *m_asm_functions.get());
|
||||
if (!codegen.CompileBlock(block, &block->host_code, &block->host_code_size))
|
||||
{
|
||||
Log_ErrorPrintf("Failed to compile host code for block at 0x%08X", block->key.GetPC());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CodeCache::FlushBlocksWithPageIndex(u32 page_index)
|
||||
{
|
||||
DebugAssert(page_index < CPU_CODE_CACHE_PAGE_COUNT);
|
||||
auto& blocks = m_ram_block_map[page_index];
|
||||
while (!blocks.empty())
|
||||
FlushBlock(blocks.back());
|
||||
|
||||
m_bus->ClearRAMCodePage(page_index);
|
||||
}
|
||||
|
||||
void CodeCache::FlushBlock(CodeBlock* block)
|
||||
{
|
||||
BlockMap::iterator iter = m_blocks.find(block->key.GetPC());
|
||||
Assert(iter != m_blocks.end() && iter->second == block);
|
||||
Log_DevPrintf("Flushing block at address 0x%08X", block->GetPC());
|
||||
|
||||
// remove from the page map
|
||||
const u32 start_page = block->GetStartPageIndex();
|
||||
const u32 end_page = block->GetEndPageIndex();
|
||||
for (u32 page = start_page; page < end_page; page++)
|
||||
{
|
||||
auto& page_blocks = m_ram_block_map[page];
|
||||
auto page_block_iter = std::find(page_blocks.begin(), page_blocks.end(), block);
|
||||
Assert(page_block_iter != page_blocks.end());
|
||||
page_blocks.erase(page_block_iter);
|
||||
}
|
||||
|
||||
// remove from block map
|
||||
m_blocks.erase(iter);
|
||||
|
||||
// flushing block currently executing?
|
||||
if (m_current_block == block)
|
||||
{
|
||||
Log_WarningPrintf("Flushing currently-executing block 0x%08X", block->GetPC());
|
||||
m_current_block_flushed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
delete block;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeCache::InterpretCachedBlock(const CodeBlock& block)
|
||||
{
|
||||
// set up the state so we've already fetched the instruction
|
||||
DebugAssert((m_core->m_regs.pc & PHYSICAL_MEMORY_ADDRESS_MASK) == block.GetPC());
|
||||
|
||||
for (const CodeBlockInstruction& cbi : block.instructions)
|
||||
{
|
||||
m_core->m_pending_ticks += 1;
|
||||
m_core->m_downcount -= 1;
|
||||
|
||||
// now executing the instruction we previously fetched
|
||||
m_core->m_current_instruction.bits = cbi.instruction.bits;
|
||||
m_core->m_current_instruction_pc = m_core->m_regs.pc;
|
||||
m_core->m_current_instruction_in_branch_delay_slot = cbi.is_branch_delay_slot;
|
||||
m_core->m_current_instruction_was_branch_taken = m_core->m_branch_was_taken;
|
||||
m_core->m_branch_was_taken = false;
|
||||
m_core->m_exception_raised = false;
|
||||
|
||||
// update pc
|
||||
DebugAssert((m_core->m_regs.pc & PHYSICAL_MEMORY_ADDRESS_MASK) == cbi.pc);
|
||||
m_core->m_regs.pc = m_core->m_regs.npc;
|
||||
m_core->m_regs.npc += 4;
|
||||
|
||||
// execute the instruction we previously fetched
|
||||
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;
|
||||
|
||||
if (m_core->m_exception_raised)
|
||||
break;
|
||||
}
|
||||
|
||||
// cleanup so the interpreter can kick in if needed
|
||||
m_core->m_next_instruction_is_branch_delay_slot = false;
|
||||
}
|
||||
|
||||
void CodeCache::InterpretUncachedBlock()
|
||||
{
|
||||
// At this point, pc contains the last address executed (in the previous block). The instruction has not been fetched
|
||||
// yet. pc shouldn't be updated until the fetch occurs, that way the exception occurs in the delay slot.
|
||||
bool in_branch_delay_slot = false;
|
||||
for (;;)
|
||||
{
|
||||
m_core->m_pending_ticks += 1;
|
||||
m_core->m_downcount -= 1;
|
||||
|
||||
// now executing the instruction we previously fetched
|
||||
m_core->m_current_instruction.bits = m_core->m_next_instruction.bits;
|
||||
m_core->m_current_instruction_pc = m_core->m_regs.pc;
|
||||
m_core->m_current_instruction_in_branch_delay_slot = m_core->m_next_instruction_is_branch_delay_slot;
|
||||
m_core->m_current_instruction_was_branch_taken = m_core->m_branch_was_taken;
|
||||
m_core->m_next_instruction_is_branch_delay_slot = false;
|
||||
m_core->m_branch_was_taken = false;
|
||||
m_core->m_exception_raised = false;
|
||||
|
||||
// Fetch the next instruction, except if we're in a branch delay slot. The "fetch" is done in the next block.
|
||||
if (!m_core->FetchInstruction())
|
||||
break;
|
||||
|
||||
// execute the instruction we previously fetched
|
||||
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;
|
||||
|
||||
const bool branch = IsBranchInstruction(m_core->m_current_instruction);
|
||||
if (m_core->m_exception_raised || (!branch && in_branch_delay_slot) ||
|
||||
IsExitBlockInstruction(m_core->m_current_instruction))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
in_branch_delay_slot = branch;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CPU
|
||||
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include "common/bitfield.h"
|
||||
#include "cpu_types.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class JitCodeBuffer;
|
||||
|
||||
class Bus;
|
||||
|
||||
namespace CPU {
|
||||
class Core;
|
||||
|
||||
namespace Recompiler {
|
||||
class ASMFunctions;
|
||||
}
|
||||
|
||||
class CodeCache
|
||||
{
|
||||
public:
|
||||
CodeCache();
|
||||
~CodeCache();
|
||||
|
||||
void Initialize(Core* core, Bus* bus);
|
||||
void Reset();
|
||||
void Execute();
|
||||
|
||||
/// Flushes all blocks which are in the range of the specified code page.
|
||||
void FlushBlocksWithPageIndex(u32 page_index);
|
||||
|
||||
private:
|
||||
using BlockMap = std::unordered_map<u32, CodeBlock*>;
|
||||
|
||||
const CodeBlock* GetNextBlock();
|
||||
bool CompileBlock(CodeBlock* block);
|
||||
void FlushBlock(CodeBlock* block);
|
||||
void InterpretCachedBlock(const CodeBlock& block);
|
||||
void InterpretUncachedBlock();
|
||||
|
||||
Core* m_core;
|
||||
Bus* m_bus;
|
||||
|
||||
const CodeBlock* m_current_block = nullptr;
|
||||
bool m_current_block_flushed = false;
|
||||
|
||||
std::unique_ptr<JitCodeBuffer> m_code_buffer;
|
||||
std::unique_ptr<Recompiler::ASMFunctions> m_asm_functions;
|
||||
|
||||
BlockMap m_blocks;
|
||||
|
||||
std::array<std::vector<CodeBlock*>, CPU_CODE_CACHE_PAGE_COUNT> m_ram_block_map;
|
||||
};
|
||||
|
||||
extern bool USE_CODE_CACHE;
|
||||
extern bool USE_RECOMPILER;
|
||||
|
||||
} // namespace CPU
|
||||
@ -0,0 +1,589 @@
|
||||
#include "cpu_recompiler_code_generator.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "cpu_disasm.h"
|
||||
Log_SetChannel(CPU::Recompiler);
|
||||
|
||||
namespace CPU::Recompiler {
|
||||
|
||||
CodeGenerator::CodeGenerator(Core* cpu, JitCodeBuffer* code_buffer, const ASMFunctions& asm_functions)
|
||||
: m_cpu(cpu), m_code_buffer(code_buffer), m_asm_functions(asm_functions), m_register_cache(*this),
|
||||
m_emit(code_buffer->GetFreeCodeSpace(), code_buffer->GetFreeCodePointer())
|
||||
{
|
||||
InitHostRegs();
|
||||
}
|
||||
|
||||
CodeGenerator::~CodeGenerator() = default;
|
||||
|
||||
u32 CodeGenerator::CalculateRegisterOffset(Reg reg)
|
||||
{
|
||||
return uint32(offsetof(Core, m_regs.r[0]) + (static_cast<u32>(reg) * sizeof(u32)));
|
||||
}
|
||||
|
||||
bool CodeGenerator::CompileBlock(const CodeBlock* block, CodeBlock::HostCodePointer* out_host_code,
|
||||
u32* out_host_code_size)
|
||||
{
|
||||
// TODO: Align code buffer.
|
||||
|
||||
m_block = block;
|
||||
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();
|
||||
|
||||
const CodeBlockInstruction* cbi = m_block_start;
|
||||
while (cbi != m_block_end)
|
||||
{
|
||||
#ifndef Y_BUILD_CONFIG_RELEASE
|
||||
SmallString disasm;
|
||||
DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits, nullptr);
|
||||
Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray());
|
||||
#endif
|
||||
|
||||
if (!CompileInstruction(*cbi))
|
||||
{
|
||||
m_block_end = nullptr;
|
||||
m_block_start = nullptr;
|
||||
m_block = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
cbi++;
|
||||
}
|
||||
|
||||
BlockEpilogue();
|
||||
EmitEndBlock();
|
||||
|
||||
FinalizeBlock(out_host_code, out_host_code_size);
|
||||
|
||||
DebugAssert(m_register_cache.GetUsedHostRegisters() == 0);
|
||||
|
||||
m_block_end = nullptr;
|
||||
m_block_start = nullptr;
|
||||
m_block = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CodeGenerator::CompileInstruction(const CodeBlockInstruction& cbi)
|
||||
{
|
||||
bool result;
|
||||
switch (cbi.instruction.op)
|
||||
{
|
||||
#if 1
|
||||
case InstructionOp::lui:
|
||||
result = Compile_lui(cbi);
|
||||
break;
|
||||
|
||||
case InstructionOp::ori:
|
||||
result = Compile_ori(cbi);
|
||||
break;
|
||||
|
||||
case InstructionOp::addiu:
|
||||
result = Compile_addiu(cbi);
|
||||
break;
|
||||
|
||||
case InstructionOp::funct:
|
||||
{
|
||||
switch (cbi.instruction.r.funct)
|
||||
{
|
||||
case InstructionFunct::sll:
|
||||
result = Compile_sll(cbi);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = Compile_Fallback(cbi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
result = Compile_Fallback(cbi);
|
||||
break;
|
||||
}
|
||||
|
||||
// release temporary effective addresses
|
||||
for (Value& value : m_operand_memory_addresses)
|
||||
value.ReleaseAndClear();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Value CodeGenerator::ConvertValueSize(const Value& value, RegSize size, bool sign_extend)
|
||||
{
|
||||
DebugAssert(value.size != size);
|
||||
|
||||
if (value.IsConstant())
|
||||
{
|
||||
// compile-time conversion, woo!
|
||||
switch (size)
|
||||
{
|
||||
case RegSize_8:
|
||||
return Value::FromConstantU8(value.constant_value & 0xFF);
|
||||
|
||||
case RegSize_16:
|
||||
{
|
||||
switch (value.size)
|
||||
{
|
||||
case RegSize_8:
|
||||
return Value::FromConstantU16(sign_extend ? SignExtend16(Truncate8(value.constant_value)) :
|
||||
ZeroExtend16(Truncate8(value.constant_value)));
|
||||
|
||||
default:
|
||||
return Value::FromConstantU16(value.constant_value & 0xFFFF);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RegSize_32:
|
||||
{
|
||||
switch (value.size)
|
||||
{
|
||||
case RegSize_8:
|
||||
return Value::FromConstantU32(sign_extend ? SignExtend32(Truncate8(value.constant_value)) :
|
||||
ZeroExtend32(Truncate8(value.constant_value)));
|
||||
case RegSize_16:
|
||||
return Value::FromConstantU32(sign_extend ? SignExtend32(Truncate16(value.constant_value)) :
|
||||
ZeroExtend32(Truncate16(value.constant_value)));
|
||||
|
||||
case RegSize_32:
|
||||
return value;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
UnreachableCode();
|
||||
return Value{};
|
||||
}
|
||||
|
||||
Value new_value = m_register_cache.AllocateScratch(size);
|
||||
if (size < value.size)
|
||||
{
|
||||
EmitCopyValue(new_value.host_reg, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sign_extend)
|
||||
EmitSignExtend(new_value.host_reg, size, value.host_reg, value.size);
|
||||
else
|
||||
EmitZeroExtend(new_value.host_reg, size, value.host_reg, value.size);
|
||||
}
|
||||
|
||||
return new_value;
|
||||
}
|
||||
|
||||
void CodeGenerator::ConvertValueSizeInPlace(Value* value, RegSize size, bool sign_extend)
|
||||
{
|
||||
DebugAssert(value->size != size);
|
||||
|
||||
// We don't want to mess up the register cache value, so generate a new value if it's not scratch.
|
||||
if (value->IsConstant() || !value->IsScratch())
|
||||
{
|
||||
*value = ConvertValueSize(*value, size, sign_extend);
|
||||
return;
|
||||
}
|
||||
|
||||
DebugAssert(value->IsInHostRegister() && value->IsScratch());
|
||||
|
||||
// If the size is smaller and the value is in a register, we can just "view" the lower part.
|
||||
if (size < value->size)
|
||||
{
|
||||
value->size = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sign_extend)
|
||||
EmitSignExtend(value->host_reg, size, value->host_reg, value->size);
|
||||
else
|
||||
EmitZeroExtend(value->host_reg, size, value->host_reg, value->size);
|
||||
}
|
||||
|
||||
value->size = size;
|
||||
}
|
||||
|
||||
Value CodeGenerator::AddValues(const Value& lhs, const Value& rhs)
|
||||
{
|
||||
DebugAssert(lhs.size == rhs.size);
|
||||
if (lhs.IsConstant() && rhs.IsConstant())
|
||||
{
|
||||
// compile-time
|
||||
u64 new_cv = lhs.constant_value + rhs.constant_value;
|
||||
switch (lhs.size)
|
||||
{
|
||||
case RegSize_8:
|
||||
return Value::FromConstantU8(Truncate8(new_cv));
|
||||
|
||||
case RegSize_16:
|
||||
return Value::FromConstantU16(Truncate16(new_cv));
|
||||
|
||||
case RegSize_32:
|
||||
return Value::FromConstantU32(Truncate32(new_cv));
|
||||
|
||||
case RegSize_64:
|
||||
return Value::FromConstantU64(new_cv);
|
||||
|
||||
default:
|
||||
return Value();
|
||||
}
|
||||
}
|
||||
|
||||
Value res = m_register_cache.AllocateScratch(lhs.size);
|
||||
if (lhs.HasConstantValue(0))
|
||||
{
|
||||
EmitCopyValue(res.host_reg, rhs);
|
||||
return res;
|
||||
}
|
||||
else if (rhs.HasConstantValue(0))
|
||||
{
|
||||
EmitCopyValue(res.host_reg, lhs);
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitCopyValue(res.host_reg, lhs);
|
||||
EmitAdd(res.host_reg, rhs);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
Value CodeGenerator::ShlValues(const Value& lhs, const Value& rhs)
|
||||
{
|
||||
DebugAssert(lhs.size == rhs.size);
|
||||
if (lhs.IsConstant() && rhs.IsConstant())
|
||||
{
|
||||
// compile-time
|
||||
u64 new_cv = lhs.constant_value << rhs.constant_value;
|
||||
switch (lhs.size)
|
||||
{
|
||||
case RegSize_8:
|
||||
return Value::FromConstantU8(Truncate8(new_cv));
|
||||
|
||||
case RegSize_16:
|
||||
return Value::FromConstantU16(Truncate16(new_cv));
|
||||
|
||||
case RegSize_32:
|
||||
return Value::FromConstantU32(Truncate32(new_cv));
|
||||
|
||||
case RegSize_64:
|
||||
return Value::FromConstantU64(new_cv);
|
||||
|
||||
default:
|
||||
return Value();
|
||||
}
|
||||
}
|
||||
|
||||
Value res = m_register_cache.AllocateScratch(lhs.size);
|
||||
EmitCopyValue(res.host_reg, lhs);
|
||||
if (!rhs.HasConstantValue(0))
|
||||
EmitShl(res.host_reg, res.size, rhs);
|
||||
return res;
|
||||
}
|
||||
|
||||
Value CodeGenerator::OrValues(const Value& lhs, const Value& rhs)
|
||||
{
|
||||
DebugAssert(lhs.size == rhs.size);
|
||||
if (lhs.IsConstant() && rhs.IsConstant())
|
||||
{
|
||||
// compile-time
|
||||
u64 new_cv = lhs.constant_value | rhs.constant_value;
|
||||
switch (lhs.size)
|
||||
{
|
||||
case RegSize_8:
|
||||
return Value::FromConstantU8(Truncate8(new_cv));
|
||||
|
||||
case RegSize_16:
|
||||
return Value::FromConstantU16(Truncate16(new_cv));
|
||||
|
||||
case RegSize_32:
|
||||
return Value::FromConstantU32(Truncate32(new_cv));
|
||||
|
||||
case RegSize_64:
|
||||
return Value::FromConstantU64(new_cv);
|
||||
|
||||
default:
|
||||
return Value();
|
||||
}
|
||||
}
|
||||
|
||||
Value res = m_register_cache.AllocateScratch(lhs.size);
|
||||
EmitCopyValue(res.host_reg, lhs);
|
||||
if (lhs.HasConstantValue(0))
|
||||
{
|
||||
EmitCopyValue(res.host_reg, rhs);
|
||||
return res;
|
||||
}
|
||||
else if (rhs.HasConstantValue(0))
|
||||
{
|
||||
EmitCopyValue(res.host_reg, lhs);
|
||||
return res;
|
||||
}
|
||||
|
||||
EmitCopyValue(res.host_reg, lhs);
|
||||
EmitOr(res.host_reg, rhs);
|
||||
return res;
|
||||
}
|
||||
|
||||
void CodeGenerator::BlockPrologue()
|
||||
{
|
||||
EmitStoreCPUStructField(offsetof(Core, m_exception_raised), Value::FromConstantU8(0));
|
||||
|
||||
// fetching of the first instruction...
|
||||
|
||||
// sync m_current_instruction_pc so we can simply add to it
|
||||
SyncCurrentInstructionPC();
|
||||
|
||||
// and the same for m_regs.pc
|
||||
SyncPC();
|
||||
|
||||
EmitAddCPUStructField(offsetof(Core, m_regs.npc), Value::FromConstantU32(4));
|
||||
}
|
||||
|
||||
void CodeGenerator::BlockEpilogue()
|
||||
{
|
||||
#if defined(_DEBUG) && defined(Y_CPU_X64)
|
||||
m_emit.nop();
|
||||
#endif
|
||||
|
||||
m_register_cache.FlushAllGuestRegisters(true, false);
|
||||
|
||||
// if the last instruction wasn't a fallback, we need to add its fetch
|
||||
if (m_delayed_pc_add > 0)
|
||||
{
|
||||
EmitAddCPUStructField(offsetof(Core, m_regs.npc), Value::FromConstantU32(m_delayed_pc_add));
|
||||
m_delayed_pc_add = 0;
|
||||
}
|
||||
|
||||
AddPendingCycles();
|
||||
|
||||
// TODO: correct value for is_branch_delay_slot - branches in branch delay slot.
|
||||
EmitStoreCPUStructField(offsetof(Core, m_next_instruction_is_branch_delay_slot), Value::FromConstantU8(0));
|
||||
}
|
||||
|
||||
void CodeGenerator::InstructionPrologue(const CodeBlockInstruction& cbi, TickCount cycles,
|
||||
bool force_sync /* = false */)
|
||||
{
|
||||
#if defined(_DEBUG) && defined(Y_CPU_X64)
|
||||
m_emit.nop();
|
||||
#endif
|
||||
|
||||
// reset dirty flags
|
||||
if (m_branch_was_taken_dirty)
|
||||
{
|
||||
Value temp = m_register_cache.AllocateScratch(RegSize_8);
|
||||
EmitLoadCPUStructField(temp.host_reg, RegSize_8, offsetof(Core, m_branch_was_taken));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_current_instruction_was_branch_taken), temp);
|
||||
EmitStoreCPUStructField(offsetof(Core, m_branch_was_taken), Value::FromConstantU8(0));
|
||||
m_current_instruction_was_branch_taken_dirty = true;
|
||||
m_branch_was_taken_dirty = false;
|
||||
}
|
||||
else if (m_current_instruction_was_branch_taken_dirty)
|
||||
{
|
||||
EmitStoreCPUStructField(offsetof(Core, m_current_instruction_was_branch_taken), Value::FromConstantU8(0));
|
||||
m_current_instruction_was_branch_taken_dirty = false;
|
||||
}
|
||||
|
||||
if (m_current_instruction_in_branch_delay_slot_dirty && !cbi.is_branch_delay_slot)
|
||||
{
|
||||
EmitStoreCPUStructField(offsetof(Core, m_current_instruction_in_branch_delay_slot), Value::FromConstantU8(0));
|
||||
m_current_instruction_in_branch_delay_slot_dirty = false;
|
||||
}
|
||||
|
||||
if (cbi.is_branch_delay_slot)
|
||||
{
|
||||
// m_regs.pc should be synced for the next block, as the branch wrote to npc
|
||||
SyncCurrentInstructionPC();
|
||||
SyncPC();
|
||||
|
||||
// m_current_instruction_in_branch_delay_slot = true
|
||||
EmitStoreCPUStructField(offsetof(Core, m_current_instruction_in_branch_delay_slot), Value::FromConstantU8(1));
|
||||
m_current_instruction_in_branch_delay_slot_dirty = true;
|
||||
}
|
||||
|
||||
if (!CanInstructionTrap(cbi.instruction, m_block->key.user_mode) && !force_sync)
|
||||
{
|
||||
// Defer updates for non-faulting instructions.
|
||||
m_delayed_pc_add += INSTRUCTION_SIZE;
|
||||
m_delayed_cycles_add += cycles;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_delayed_pc_add > 0)
|
||||
{
|
||||
// m_current_instruction_pc += m_delayed_pc_add
|
||||
EmitAddCPUStructField(offsetof(Core, m_current_instruction_pc), Value::FromConstantU32(m_delayed_pc_add));
|
||||
|
||||
// m_regs.pc += m_delayed_pc_add
|
||||
EmitAddCPUStructField(offsetof(Core, m_regs.pc), Value::FromConstantU32(m_delayed_pc_add));
|
||||
|
||||
// m_regs.npc += m_delayed_pc_add
|
||||
// TODO: This can go once we recompile branch instructions and unconditionally set npc
|
||||
EmitAddCPUStructField(offsetof(Core, m_regs.npc), Value::FromConstantU32(m_delayed_pc_add));
|
||||
|
||||
m_delayed_pc_add = 0;
|
||||
}
|
||||
|
||||
if (!cbi.is_branch)
|
||||
m_delayed_pc_add = INSTRUCTION_SIZE;
|
||||
|
||||
m_delayed_cycles_add += cycles;
|
||||
AddPendingCycles();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 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));
|
||||
|
||||
m_next_load_delay_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CodeGenerator::SyncCurrentInstructionPC()
|
||||
{
|
||||
// m_current_instruction_pc = m_regs.pc
|
||||
Value pc_value = m_register_cache.AllocateScratch(RegSize_32);
|
||||
EmitLoadCPUStructField(pc_value.host_reg, RegSize_32, offsetof(Core, m_regs.pc));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_current_instruction_pc), pc_value);
|
||||
}
|
||||
|
||||
void CodeGenerator::SyncPC()
|
||||
{
|
||||
// m_regs.pc = m_regs.npc
|
||||
Value npc_value = m_register_cache.AllocateScratch(RegSize_32);
|
||||
EmitLoadCPUStructField(npc_value.host_reg, RegSize_32, offsetof(Core, m_regs.npc));
|
||||
EmitStoreCPUStructField(offsetof(Core, m_regs.pc), npc_value);
|
||||
}
|
||||
|
||||
void CodeGenerator::AddPendingCycles()
|
||||
{
|
||||
if (m_delayed_cycles_add == 0)
|
||||
return;
|
||||
|
||||
EmitAddCPUStructField(offsetof(Core, m_pending_ticks), Value::FromConstantU32(m_delayed_cycles_add));
|
||||
EmitAddCPUStructField(offsetof(Core, m_downcount), Value::FromConstantU32(~u32(m_delayed_cycles_add - 1)));
|
||||
m_delayed_cycles_add = 0;
|
||||
}
|
||||
|
||||
bool CodeGenerator::Compile_Fallback(const CodeBlockInstruction& cbi)
|
||||
{
|
||||
InstructionPrologue(cbi, 1, true);
|
||||
|
||||
// flush and invalidate all guest registers, since the fallback could change any of them
|
||||
m_register_cache.FlushAllGuestRegisters(true, true);
|
||||
|
||||
EmitStoreCPUStructField(offsetof(Core, m_current_instruction.bits), Value::FromConstantU32(cbi.instruction.bits));
|
||||
|
||||
// emit the function call
|
||||
if (CanInstructionTrap(cbi.instruction, m_block->key.user_mode))
|
||||
{
|
||||
// TODO: Use carry flag or something here too
|
||||
Value return_value = m_register_cache.AllocateScratch(RegSize_8);
|
||||
EmitFunctionCall(&return_value, &Thunks::InterpretInstruction, m_register_cache.GetCPUPtr());
|
||||
EmitBlockExitOnBool(return_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
InstructionEpilogue(cbi);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CodeGenerator::Compile_lui(const CodeBlockInstruction& cbi)
|
||||
{
|
||||
InstructionPrologue(cbi, 1);
|
||||
|
||||
// rt <- (imm << 16)
|
||||
m_register_cache.WriteGuestRegister(cbi.instruction.i.rt,
|
||||
Value::FromConstantU32(cbi.instruction.i.imm_zext32() << 16));
|
||||
|
||||
InstructionEpilogue(cbi);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CodeGenerator::Compile_ori(const CodeBlockInstruction& cbi)
|
||||
{
|
||||
InstructionPrologue(cbi, 1);
|
||||
|
||||
// rt <- rs | zext(imm)
|
||||
m_register_cache.WriteGuestRegister(cbi.instruction.i.rt,
|
||||
OrValues(m_register_cache.ReadGuestRegister(cbi.instruction.i.rs),
|
||||
Value::FromConstantU32(cbi.instruction.i.imm_zext32())));
|
||||
|
||||
InstructionEpilogue(cbi);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CodeGenerator::Compile_sll(const CodeBlockInstruction& cbi)
|
||||
{
|
||||
InstructionPrologue(cbi, 1);
|
||||
|
||||
// rd <- rt << shamt
|
||||
m_register_cache.WriteGuestRegister(cbi.instruction.r.rd,
|
||||
ShlValues(m_register_cache.ReadGuestRegister(cbi.instruction.r.rt),
|
||||
Value::FromConstantU32(cbi.instruction.r.shamt)));
|
||||
|
||||
InstructionEpilogue(cbi);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CodeGenerator::Compile_addiu(const CodeBlockInstruction& cbi)
|
||||
{
|
||||
InstructionPrologue(cbi, 1);
|
||||
|
||||
// rt <- rs + sext(imm)
|
||||
m_register_cache.WriteGuestRegister(cbi.instruction.i.rt,
|
||||
AddValues(m_register_cache.ReadGuestRegister(cbi.instruction.i.rs),
|
||||
Value::FromConstantU32(cbi.instruction.i.imm_sext32())));
|
||||
|
||||
InstructionEpilogue(cbi);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace CPU::Recompiler
|
||||
@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
#include "common/jit_code_buffer.h"
|
||||
|
||||
#include "cpu_recompiler_register_cache.h"
|
||||
#include "cpu_recompiler_thunks.h"
|
||||
#include "cpu_recompiler_types.h"
|
||||
#include "cpu_types.h"
|
||||
|
||||
// ABI selection
|
||||
#if defined(Y_CPU_X64)
|
||||
#if defined(Y_PLATFORM_WINDOWS)
|
||||
#define ABI_WIN64 1
|
||||
#elif defined(Y_PLATFORM_LINUX) || defined(Y_PLATFORM_OSX)
|
||||
#define ABI_SYSV 1
|
||||
#else
|
||||
#error Unknown ABI.
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace CPU::Recompiler {
|
||||
|
||||
class CodeGenerator
|
||||
{
|
||||
public:
|
||||
CodeGenerator(Core* cpu, JitCodeBuffer* code_buffer, const ASMFunctions& asm_functions);
|
||||
~CodeGenerator();
|
||||
|
||||
static u32 CalculateRegisterOffset(Reg reg);
|
||||
static const char* GetHostRegName(HostReg reg, RegSize size = HostPointerSize);
|
||||
static void AlignCodeBuffer(JitCodeBuffer* code_buffer);
|
||||
|
||||
RegisterCache& GetRegisterCache() { return m_register_cache; }
|
||||
CodeEmitter& GetCodeEmitter() { return m_emit; }
|
||||
|
||||
bool CompileBlock(const CodeBlock* block, CodeBlock::HostCodePointer* out_host_code, u32* out_host_code_size);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Code Generation
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void EmitBeginBlock();
|
||||
void EmitEndBlock();
|
||||
void EmitBlockExitOnBool(const Value& value);
|
||||
void FinalizeBlock(CodeBlock::HostCodePointer* out_host_code, u32* out_host_code_size);
|
||||
|
||||
void EmitSignExtend(HostReg to_reg, RegSize to_size, HostReg from_reg, RegSize from_size);
|
||||
void EmitZeroExtend(HostReg to_reg, RegSize to_size, HostReg from_reg, RegSize from_size);
|
||||
void EmitCopyValue(HostReg to_reg, const Value& value);
|
||||
void EmitAdd(HostReg to_reg, const Value& value);
|
||||
void EmitSub(HostReg to_reg, const Value& value);
|
||||
void EmitCmp(HostReg to_reg, const Value& value);
|
||||
void EmitInc(HostReg to_reg, RegSize size);
|
||||
void EmitDec(HostReg to_reg, RegSize size);
|
||||
void EmitShl(HostReg to_reg, RegSize size, const Value& amount_value);
|
||||
void EmitShr(HostReg to_reg, RegSize size, const Value& amount_value);
|
||||
void EmitSar(HostReg to_reg, RegSize size, const Value& amount_value);
|
||||
void EmitAnd(HostReg to_reg, const Value& value);
|
||||
void EmitOr(HostReg to_reg, const Value& value);
|
||||
void EmitXor(HostReg to_reg, const Value& value);
|
||||
void EmitTest(HostReg to_reg, const Value& value);
|
||||
void EmitNot(HostReg to_reg, RegSize size);
|
||||
|
||||
void EmitLoadGuestRegister(HostReg host_reg, Reg guest_reg);
|
||||
void EmitStoreGuestRegister(Reg guest_reg, const Value& value);
|
||||
void EmitLoadCPUStructField(HostReg host_reg, RegSize size, u32 offset);
|
||||
void EmitStoreCPUStructField(u32 offset, const Value& value);
|
||||
void EmitAddCPUStructField(u32 offset, const Value& value);
|
||||
|
||||
u32 PrepareStackForCall();
|
||||
void RestoreStackAfterCall(u32 adjust_size);
|
||||
|
||||
void EmitFunctionCallPtr(Value* return_value, const void* ptr);
|
||||
void EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1);
|
||||
void EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1, const Value& arg2);
|
||||
void EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1, const Value& arg2,
|
||||
const Value& arg3);
|
||||
void EmitFunctionCallPtr(Value* return_value, const void* ptr, const Value& arg1, const Value& arg2,
|
||||
const Value& arg3, const Value& arg4);
|
||||
|
||||
template<typename FunctionType>
|
||||
void EmitFunctionCall(Value* return_value, const FunctionType ptr)
|
||||
{
|
||||
EmitFunctionCallPtr(return_value, reinterpret_cast<const void**>(ptr));
|
||||
}
|
||||
|
||||
template<typename FunctionType>
|
||||
void EmitFunctionCall(Value* return_value, const FunctionType ptr, const Value& arg1)
|
||||
{
|
||||
EmitFunctionCallPtr(return_value, reinterpret_cast<const void**>(ptr), arg1);
|
||||
}
|
||||
|
||||
template<typename FunctionType>
|
||||
void EmitFunctionCall(Value* return_value, const FunctionType ptr, const Value& arg1, const Value& arg2)
|
||||
{
|
||||
EmitFunctionCallPtr(return_value, reinterpret_cast<const void**>(ptr), arg1, arg2);
|
||||
}
|
||||
|
||||
template<typename FunctionType>
|
||||
void EmitFunctionCall(Value* return_value, const FunctionType ptr, const Value& arg1, const Value& arg2,
|
||||
const Value& arg3)
|
||||
{
|
||||
EmitFunctionCallPtr(return_value, reinterpret_cast<const void**>(ptr), arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
template<typename FunctionType>
|
||||
void EmitFunctionCall(Value* return_value, const FunctionType ptr, const Value& arg1, const Value& arg2,
|
||||
const Value& arg3, const Value& arg4)
|
||||
{
|
||||
EmitFunctionCallPtr(return_value, reinterpret_cast<const void**>(ptr), arg1, arg2, arg3, arg4);
|
||||
}
|
||||
|
||||
// Host register saving.
|
||||
void EmitPushHostReg(HostReg reg);
|
||||
void EmitPopHostReg(HostReg reg);
|
||||
|
||||
// Flags copying from host.
|
||||
#if defined(Y_CPU_X64)
|
||||
void ReadFlagsFromHost(Value* value);
|
||||
Value ReadFlagsFromHost();
|
||||
#endif
|
||||
|
||||
// Value ops
|
||||
Value AddValues(const Value& lhs, const Value& rhs);
|
||||
Value MulValues(const Value& lhs, const Value& rhs);
|
||||
Value ShlValues(const Value& lhs, const Value& rhs);
|
||||
Value OrValues(const Value& lhs, const Value& rhs);
|
||||
|
||||
private:
|
||||
// Host register setup
|
||||
void InitHostRegs();
|
||||
|
||||
Value ConvertValueSize(const Value& value, RegSize size, bool sign_extend);
|
||||
void ConvertValueSizeInPlace(Value* value, RegSize size, bool sign_extend);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Code Generation Helpers
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// branch target, memory address, etc
|
||||
void BlockPrologue();
|
||||
void BlockEpilogue();
|
||||
void InstructionPrologue(const CodeBlockInstruction& cbi, TickCount cycles,
|
||||
bool force_sync = false);
|
||||
void InstructionEpilogue(const CodeBlockInstruction& cbi);
|
||||
void SyncCurrentInstructionPC();
|
||||
void SyncPC();
|
||||
void AddPendingCycles();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Instruction Code Generators
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
bool CompileInstruction(const CodeBlockInstruction& cbi);
|
||||
bool Compile_Fallback(const CodeBlockInstruction& cbi);
|
||||
bool Compile_lui(const CodeBlockInstruction& cbi);
|
||||
bool Compile_ori(const CodeBlockInstruction& cbi);
|
||||
bool Compile_sll(const CodeBlockInstruction& cbi);
|
||||
bool Compile_addiu(const CodeBlockInstruction& cbi);
|
||||
|
||||
Core* m_cpu;
|
||||
JitCodeBuffer* m_code_buffer;
|
||||
const ASMFunctions& m_asm_functions;
|
||||
const CodeBlock* m_block = nullptr;
|
||||
const CodeBlockInstruction* m_block_start = nullptr;
|
||||
const CodeBlockInstruction* m_block_end = nullptr;
|
||||
RegisterCache m_register_cache;
|
||||
CodeEmitter m_emit;
|
||||
|
||||
u32 m_delayed_pc_add = 0;
|
||||
TickCount m_delayed_cycles_add = 0;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace CPU_X86::Recompiler
|
||||
@ -0,0 +1,21 @@
|
||||
#include "cpu_recompiler_code_generator.h"
|
||||
|
||||
namespace CPU::Recompiler {
|
||||
|
||||
#if !defined(Y_CPU_X64)
|
||||
void CodeGenerator::AlignCodeBuffer(JitCodeBuffer* code_buffer) {}
|
||||
#endif
|
||||
|
||||
void CodeGenerator::EmitLoadGuestRegister(HostReg host_reg, Reg guest_reg)
|
||||
{
|
||||
EmitLoadCPUStructField(host_reg, RegSize_32, CalculateRegisterOffset(guest_reg));
|
||||
}
|
||||
|
||||
void CodeGenerator::EmitStoreGuestRegister(Reg guest_reg, const Value& value)
|
||||
{
|
||||
DebugAssert(value.size == RegSize_32);
|
||||
EmitStoreCPUStructField(CalculateRegisterOffset(guest_reg), value);
|
||||
}
|
||||
|
||||
|
||||
} // namespace CPU::Recompiler
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,604 @@
|
||||
#include "cpu_recompiler_register_cache.h"
|
||||
#include "YBaseLib/Log.h"
|
||||
#include "cpu_recompiler_code_generator.h"
|
||||
#include <cinttypes>
|
||||
Log_SetChannel(CPU::Recompiler);
|
||||
|
||||
namespace CPU::Recompiler {
|
||||
|
||||
Value::Value() = default;
|
||||
|
||||
Value::Value(RegisterCache* regcache_, u64 constant_, RegSize size_, ValueFlags flags_)
|
||||
: regcache(regcache_), constant_value(constant_), size(size_), flags(flags_)
|
||||
{
|
||||
}
|
||||
|
||||
Value::Value(const Value& other)
|
||||
: regcache(other.regcache), constant_value(other.constant_value), host_reg(other.host_reg), size(other.size),
|
||||
flags(other.flags)
|
||||
{
|
||||
AssertMsg(!other.IsScratch(), "Can't copy a temporary register");
|
||||
}
|
||||
|
||||
Value::Value(Value&& other)
|
||||
: regcache(other.regcache), constant_value(other.constant_value), host_reg(other.host_reg), size(other.size),
|
||||
flags(other.flags)
|
||||
{
|
||||
other.Clear();
|
||||
}
|
||||
|
||||
Value::Value(RegisterCache* regcache_, HostReg reg_, RegSize size_, ValueFlags flags_)
|
||||
: regcache(regcache_), host_reg(reg_), size(size_), flags(flags_)
|
||||
{
|
||||
}
|
||||
|
||||
Value::~Value()
|
||||
{
|
||||
Release();
|
||||
}
|
||||
|
||||
Value& Value::operator=(const Value& other)
|
||||
{
|
||||
AssertMsg(!other.IsScratch(), "Can't copy a temporary register");
|
||||
|
||||
Release();
|
||||
regcache = other.regcache;
|
||||
constant_value = other.constant_value;
|
||||
host_reg = other.host_reg;
|
||||
size = other.size;
|
||||
flags = other.flags;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Value& Value::operator=(Value&& other)
|
||||
{
|
||||
Release();
|
||||
regcache = other.regcache;
|
||||
constant_value = other.constant_value;
|
||||
host_reg = other.host_reg;
|
||||
size = other.size;
|
||||
flags = other.flags;
|
||||
other.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Value::Clear()
|
||||
{
|
||||
regcache = nullptr;
|
||||
constant_value = 0;
|
||||
host_reg = {};
|
||||
size = RegSize_8;
|
||||
flags = ValueFlags::None;
|
||||
}
|
||||
|
||||
void Value::Release()
|
||||
{
|
||||
if (IsScratch())
|
||||
{
|
||||
DebugAssert(IsInHostRegister() && regcache);
|
||||
regcache->FreeHostReg(host_reg);
|
||||
}
|
||||
}
|
||||
|
||||
void Value::ReleaseAndClear()
|
||||
{
|
||||
Release();
|
||||
Clear();
|
||||
}
|
||||
|
||||
void Value::Discard()
|
||||
{
|
||||
DebugAssert(IsInHostRegister());
|
||||
regcache->DiscardHostReg(host_reg);
|
||||
}
|
||||
|
||||
void Value::Undiscard()
|
||||
{
|
||||
DebugAssert(IsInHostRegister());
|
||||
regcache->UndiscardHostReg(host_reg);
|
||||
}
|
||||
|
||||
RegisterCache::RegisterCache(CodeGenerator& code_generator) : m_code_generator(code_generator)
|
||||
{
|
||||
m_guest_register_order.fill(Reg::count);
|
||||
}
|
||||
|
||||
RegisterCache::~RegisterCache() = default;
|
||||
|
||||
void RegisterCache::SetHostRegAllocationOrder(std::initializer_list<HostReg> regs)
|
||||
{
|
||||
size_t index = 0;
|
||||
for (HostReg reg : regs)
|
||||
{
|
||||
m_host_register_state[reg] = HostRegState::Usable;
|
||||
m_host_register_allocation_order[index++] = reg;
|
||||
}
|
||||
m_host_register_available_count = static_cast<u32>(index);
|
||||
}
|
||||
|
||||
void RegisterCache::SetCallerSavedHostRegs(std::initializer_list<HostReg> regs)
|
||||
{
|
||||
for (HostReg reg : regs)
|
||||
m_host_register_state[reg] |= HostRegState::CallerSaved;
|
||||
}
|
||||
|
||||
void RegisterCache::SetCalleeSavedHostRegs(std::initializer_list<HostReg> regs)
|
||||
{
|
||||
for (HostReg reg : regs)
|
||||
m_host_register_state[reg] |= HostRegState::CalleeSaved;
|
||||
}
|
||||
|
||||
void RegisterCache::SetCPUPtrHostReg(HostReg reg)
|
||||
{
|
||||
m_cpu_ptr_host_register = reg;
|
||||
}
|
||||
|
||||
bool RegisterCache::IsUsableHostReg(HostReg reg) const
|
||||
{
|
||||
return (m_host_register_state[reg] & HostRegState::Usable) != HostRegState::None;
|
||||
}
|
||||
|
||||
bool RegisterCache::IsHostRegInUse(HostReg reg) const
|
||||
{
|
||||
return (m_host_register_state[reg] & HostRegState::InUse) != HostRegState::None;
|
||||
}
|
||||
|
||||
bool RegisterCache::HasFreeHostRegister() const
|
||||
{
|
||||
for (const HostRegState state : m_host_register_state)
|
||||
{
|
||||
if ((state & (HostRegState::Usable | HostRegState::InUse)) == (HostRegState::Usable))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 RegisterCache::GetUsedHostRegisters() const
|
||||
{
|
||||
u32 count = 0;
|
||||
for (const HostRegState state : m_host_register_state)
|
||||
{
|
||||
if ((state & (HostRegState::Usable | HostRegState::InUse)) == (HostRegState::Usable | HostRegState::InUse))
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 RegisterCache::GetFreeHostRegisters() const
|
||||
{
|
||||
u32 count = 0;
|
||||
for (const HostRegState state : m_host_register_state)
|
||||
{
|
||||
if ((state & (HostRegState::Usable | HostRegState::InUse)) == (HostRegState::Usable))
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
HostReg RegisterCache::AllocateHostReg(HostRegState state /* = HostRegState::InUse */)
|
||||
{
|
||||
// try for a free register in allocation order
|
||||
for (u32 i = 0; i < m_host_register_available_count; i++)
|
||||
{
|
||||
const HostReg reg = m_host_register_allocation_order[i];
|
||||
if ((m_host_register_state[reg] & (HostRegState::Usable | HostRegState::InUse)) == HostRegState::Usable)
|
||||
{
|
||||
if (AllocateHostReg(reg, state))
|
||||
return reg;
|
||||
}
|
||||
}
|
||||
|
||||
// evict one of the cached guest registers
|
||||
if (!EvictOneGuestRegister())
|
||||
Panic("Failed to evict guest register for new allocation");
|
||||
|
||||
return AllocateHostReg(state);
|
||||
}
|
||||
|
||||
bool RegisterCache::AllocateHostReg(HostReg reg, HostRegState state /*= HostRegState::InUse*/)
|
||||
{
|
||||
if ((m_host_register_state[reg] & HostRegState::InUse) == HostRegState::InUse)
|
||||
return false;
|
||||
|
||||
m_host_register_state[reg] |= state;
|
||||
|
||||
if ((m_host_register_state[reg] & (HostRegState::CalleeSaved | HostRegState::CalleeSavedAllocated)) ==
|
||||
HostRegState::CalleeSaved)
|
||||
{
|
||||
// new register we need to save..
|
||||
DebugAssert(m_host_register_callee_saved_order_count < HostReg_Count);
|
||||
m_host_register_callee_saved_order[m_host_register_callee_saved_order_count++] = reg;
|
||||
m_host_register_state[reg] |= HostRegState::CalleeSavedAllocated;
|
||||
m_code_generator.EmitPushHostReg(reg);
|
||||
}
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
void RegisterCache::DiscardHostReg(HostReg reg)
|
||||
{
|
||||
DebugAssert(IsHostRegInUse(reg));
|
||||
Log_DebugPrintf("Discarding host register %s", m_code_generator.GetHostRegName(reg));
|
||||
m_host_register_state[reg] |= HostRegState::Discarded;
|
||||
}
|
||||
|
||||
void RegisterCache::UndiscardHostReg(HostReg reg)
|
||||
{
|
||||
DebugAssert(IsHostRegInUse(reg));
|
||||
Log_DebugPrintf("Undiscarding host register %s", m_code_generator.GetHostRegName(reg));
|
||||
m_host_register_state[reg] &= ~HostRegState::Discarded;
|
||||
}
|
||||
|
||||
void RegisterCache::FreeHostReg(HostReg reg)
|
||||
{
|
||||
DebugAssert(IsHostRegInUse(reg));
|
||||
Log_DebugPrintf("Freeing host register %s", m_code_generator.GetHostRegName(reg));
|
||||
m_host_register_state[reg] &= ~HostRegState::InUse;
|
||||
}
|
||||
|
||||
void RegisterCache::EnsureHostRegFree(HostReg reg)
|
||||
{
|
||||
if (!IsHostRegInUse(reg))
|
||||
return;
|
||||
|
||||
for (u8 i = 0; i < static_cast<u8>(Reg::count); i++)
|
||||
{
|
||||
if (m_guest_reg_cache[i].IsInHostRegister() && m_guest_reg_cache[i].GetHostRegister() == reg)
|
||||
FlushGuestRegister(m_guest_reg_cache[i], static_cast<Reg>(i), true, true);
|
||||
}
|
||||
}
|
||||
|
||||
Value RegisterCache::GetCPUPtr()
|
||||
{
|
||||
return Value::FromHostReg(this, m_cpu_ptr_host_register, HostPointerSize);
|
||||
}
|
||||
|
||||
Value RegisterCache::AllocateScratch(RegSize size, HostReg reg /* = HostReg_Invalid */)
|
||||
{
|
||||
if (reg == HostReg_Invalid)
|
||||
{
|
||||
reg = AllocateHostReg();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(!IsHostRegInUse(reg));
|
||||
if (!AllocateHostReg(reg))
|
||||
Panic("Failed to allocate specific host register");
|
||||
}
|
||||
|
||||
Log_DebugPrintf("Allocating host register %s as scratch", m_code_generator.GetHostRegName(reg));
|
||||
return Value::FromScratch(this, reg, size);
|
||||
}
|
||||
|
||||
u32 RegisterCache::PushCallerSavedRegisters() const
|
||||
{
|
||||
u32 count = 0;
|
||||
for (u32 i = 0; i < HostReg_Count; i++)
|
||||
{
|
||||
if ((m_host_register_state[i] & (HostRegState::CallerSaved | HostRegState::InUse | HostRegState::Discarded)) ==
|
||||
(HostRegState::CallerSaved | HostRegState::InUse))
|
||||
{
|
||||
m_code_generator.EmitPushHostReg(static_cast<HostReg>(i));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 RegisterCache::PopCallerSavedRegisters() const
|
||||
{
|
||||
u32 count = 0;
|
||||
u32 i = (HostReg_Count - 1);
|
||||
do
|
||||
{
|
||||
if ((m_host_register_state[i] & (HostRegState::CallerSaved | HostRegState::InUse | HostRegState::Discarded)) ==
|
||||
(HostRegState::CallerSaved | HostRegState::InUse))
|
||||
{
|
||||
m_code_generator.EmitPopHostReg(static_cast<HostReg>(i));
|
||||
count++;
|
||||
}
|
||||
i--;
|
||||
} while (i > 0);
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 RegisterCache::PopCalleeSavedRegisters()
|
||||
{
|
||||
if (m_host_register_callee_saved_order_count == 0)
|
||||
return 0;
|
||||
|
||||
u32 count = 0;
|
||||
u32 i = m_host_register_callee_saved_order_count;
|
||||
do
|
||||
{
|
||||
const HostReg reg = m_host_register_callee_saved_order[i - 1];
|
||||
DebugAssert((m_host_register_state[reg] & (HostRegState::CalleeSaved | HostRegState::CalleeSavedAllocated)) ==
|
||||
(HostRegState::CalleeSaved | HostRegState::CalleeSavedAllocated));
|
||||
|
||||
m_code_generator.EmitPopHostReg(reg);
|
||||
m_host_register_state[reg] &= ~HostRegState::CalleeSavedAllocated;
|
||||
count++;
|
||||
i--;
|
||||
} while (i > 0);
|
||||
return count;
|
||||
}
|
||||
|
||||
Value RegisterCache::ReadGuestRegister(Reg guest_reg, bool cache /* = true */, bool force_host_register /* = false */,
|
||||
HostReg forced_host_reg /* = HostReg_Invalid */)
|
||||
{
|
||||
return ReadGuestRegister(m_guest_reg_cache[static_cast<u8>(guest_reg)], guest_reg, cache, force_host_register,
|
||||
forced_host_reg);
|
||||
}
|
||||
|
||||
Value RegisterCache::ReadGuestRegister(Value& cache_value, Reg guest_reg, bool cache, bool force_host_register,
|
||||
HostReg forced_host_reg)
|
||||
{
|
||||
// register zero is always zero
|
||||
if (guest_reg == Reg::zero)
|
||||
return Value::FromConstantU32(0);
|
||||
|
||||
if (cache_value.IsValid())
|
||||
{
|
||||
if (cache_value.IsInHostRegister())
|
||||
{
|
||||
PushRegisterToOrder(guest_reg);
|
||||
|
||||
// if it's in the wrong register, return it as scratch
|
||||
if (forced_host_reg == HostReg_Invalid || cache_value.GetHostRegister() == forced_host_reg)
|
||||
return cache_value;
|
||||
|
||||
Value temp = AllocateScratch(RegSize_32, forced_host_reg);
|
||||
m_code_generator.EmitCopyValue(forced_host_reg, cache_value);
|
||||
return temp;
|
||||
}
|
||||
else if (force_host_register)
|
||||
{
|
||||
// if it's not in a register, it should be constant
|
||||
DebugAssert(cache_value.IsConstant());
|
||||
|
||||
HostReg host_reg;
|
||||
if (forced_host_reg == HostReg_Invalid)
|
||||
{
|
||||
host_reg = AllocateHostReg();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(!IsHostRegInUse(forced_host_reg));
|
||||
if (!AllocateHostReg(forced_host_reg))
|
||||
Panic("Failed to allocate specific host register");
|
||||
host_reg = forced_host_reg;
|
||||
}
|
||||
|
||||
Log_DebugPrintf("Allocated host register %s for constant guest register %s (0x%" PRIX64 ")",
|
||||
m_code_generator.GetHostRegName(host_reg), GetRegName(guest_reg), cache_value.constant_value);
|
||||
|
||||
m_code_generator.EmitCopyValue(host_reg, cache_value);
|
||||
cache_value.AddHostReg(this, host_reg);
|
||||
AppendRegisterToOrder(guest_reg);
|
||||
|
||||
// if we're forcing a host register, we're probably going to be changing the value,
|
||||
// in which case the constant won't be correct anyway. so just drop it.
|
||||
cache_value.ClearConstant();
|
||||
return cache_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// constant
|
||||
return cache_value;
|
||||
}
|
||||
}
|
||||
|
||||
HostReg host_reg;
|
||||
if (forced_host_reg == HostReg_Invalid)
|
||||
{
|
||||
host_reg = AllocateHostReg();
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert(!IsHostRegInUse(forced_host_reg));
|
||||
if (!AllocateHostReg(forced_host_reg))
|
||||
Panic("Failed to allocate specific host register");
|
||||
host_reg = forced_host_reg;
|
||||
}
|
||||
|
||||
m_code_generator.EmitLoadGuestRegister(host_reg, guest_reg);
|
||||
|
||||
Log_DebugPrintf("Loading guest register %s to host register %s%s", GetRegName(guest_reg),
|
||||
m_code_generator.GetHostRegName(host_reg, RegSize_32), cache ? " (cached)" : "");
|
||||
|
||||
if (cache)
|
||||
{
|
||||
// Now in cache.
|
||||
cache_value.SetHostReg(this, host_reg, RegSize_32);
|
||||
AppendRegisterToOrder(guest_reg);
|
||||
return cache_value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip caching, return the register as a value.
|
||||
return Value::FromScratch(this, host_reg, RegSize_32);
|
||||
}
|
||||
}
|
||||
|
||||
Value RegisterCache::WriteGuestRegister(Reg guest_reg, Value&& value)
|
||||
{
|
||||
return WriteGuestRegister(m_guest_reg_cache[static_cast<u8>(guest_reg)], guest_reg, std::move(value));
|
||||
}
|
||||
|
||||
Value RegisterCache::WriteGuestRegister(Value& cache_value, Reg guest_reg, Value&& value)
|
||||
{
|
||||
// ignore writes to register zero
|
||||
if (guest_reg == Reg::zero)
|
||||
return std::move(value);
|
||||
|
||||
DebugAssert(value.size == RegSize_32);
|
||||
if (cache_value.IsInHostRegister() && value.IsInHostRegister() && cache_value.host_reg == value.host_reg)
|
||||
{
|
||||
// updating the register value.
|
||||
Log_DebugPrintf("Updating guest register %s (in host register %s)", GetRegName(guest_reg),
|
||||
m_code_generator.GetHostRegName(value.host_reg, RegSize_32));
|
||||
cache_value = std::move(value);
|
||||
cache_value.SetDirty();
|
||||
return cache_value;
|
||||
}
|
||||
|
||||
InvalidateGuestRegister(cache_value, guest_reg);
|
||||
DebugAssert(!cache_value.IsValid());
|
||||
|
||||
if (value.IsConstant())
|
||||
{
|
||||
// No need to allocate a host register, and we can defer the store.
|
||||
cache_value = value;
|
||||
cache_value.SetDirty();
|
||||
return cache_value;
|
||||
}
|
||||
|
||||
AppendRegisterToOrder(guest_reg);
|
||||
|
||||
// If it's a temporary, we can bind that to the guest register.
|
||||
if (value.IsScratch())
|
||||
{
|
||||
Log_DebugPrintf("Binding scratch register %s to guest register %s",
|
||||
m_code_generator.GetHostRegName(value.host_reg, RegSize_32), GetRegName(guest_reg));
|
||||
|
||||
cache_value = std::move(value);
|
||||
cache_value.flags &= ~ValueFlags::Scratch;
|
||||
cache_value.SetDirty();
|
||||
return Value::FromHostReg(this, cache_value.host_reg, RegSize_32);
|
||||
}
|
||||
|
||||
// Allocate host register, and copy value to it.
|
||||
HostReg host_reg = AllocateHostReg();
|
||||
m_code_generator.EmitCopyValue(host_reg, value);
|
||||
cache_value.SetHostReg(this, host_reg, RegSize_32);
|
||||
cache_value.SetDirty();
|
||||
|
||||
Log_DebugPrintf("Copying non-scratch register %s to %s to guest register %s",
|
||||
m_code_generator.GetHostRegName(value.host_reg, RegSize_32),
|
||||
m_code_generator.GetHostRegName(host_reg, RegSize_32), GetRegName(guest_reg));
|
||||
|
||||
return Value::FromHostReg(this, cache_value.host_reg, RegSize_32);
|
||||
}
|
||||
|
||||
void RegisterCache::FlushGuestRegister(Reg guest_reg, bool invalidate, bool clear_dirty)
|
||||
{
|
||||
FlushGuestRegister(m_guest_reg_cache[static_cast<u8>(guest_reg)], guest_reg, invalidate, clear_dirty);
|
||||
}
|
||||
|
||||
void RegisterCache::FlushGuestRegister(Value& cache_value, Reg guest_reg, bool invalidate, bool clear_dirty)
|
||||
{
|
||||
if (cache_value.IsDirty())
|
||||
{
|
||||
if (cache_value.IsInHostRegister())
|
||||
{
|
||||
Log_DebugPrintf("Flushing guest register %s from host register %s", GetRegName(guest_reg),
|
||||
m_code_generator.GetHostRegName(cache_value.host_reg, RegSize_32));
|
||||
}
|
||||
else if (cache_value.IsConstant())
|
||||
{
|
||||
Log_DebugPrintf("Flushing guest register %s from constant 0x%" PRIX64, GetRegName(guest_reg),
|
||||
cache_value.constant_value);
|
||||
}
|
||||
m_code_generator.EmitStoreGuestRegister(guest_reg, cache_value);
|
||||
if (clear_dirty)
|
||||
cache_value.ClearDirty();
|
||||
}
|
||||
|
||||
if (invalidate)
|
||||
InvalidateGuestRegister(cache_value, guest_reg);
|
||||
}
|
||||
|
||||
void RegisterCache::InvalidateGuestRegister(Reg guest_reg)
|
||||
{
|
||||
InvalidateGuestRegister(m_guest_reg_cache[static_cast<u8>(guest_reg)], guest_reg);
|
||||
}
|
||||
|
||||
void RegisterCache::InvalidateGuestRegister(Value& cache_value, Reg guest_reg)
|
||||
{
|
||||
if (!cache_value.IsValid())
|
||||
return;
|
||||
|
||||
if (cache_value.IsInHostRegister())
|
||||
{
|
||||
FreeHostReg(cache_value.host_reg);
|
||||
ClearRegisterFromOrder(guest_reg);
|
||||
}
|
||||
|
||||
Log_DebugPrintf("Invalidating guest register %s", GetRegName(guest_reg));
|
||||
cache_value.Clear();
|
||||
}
|
||||
|
||||
void RegisterCache::FlushAllGuestRegisters(bool invalidate, bool clear_dirty)
|
||||
{
|
||||
for (u8 reg = 0; reg < static_cast<u8>(Reg::count); reg++)
|
||||
FlushGuestRegister(static_cast<Reg>(reg), invalidate, clear_dirty);
|
||||
}
|
||||
|
||||
bool RegisterCache::EvictOneGuestRegister()
|
||||
{
|
||||
if (m_guest_register_order_count == 0)
|
||||
return false;
|
||||
|
||||
// evict the register used the longest time ago
|
||||
Reg evict_reg = m_guest_register_order[m_guest_register_order_count - 1];
|
||||
Log_ProfilePrintf("Evicting guest register %s", GetRegName(evict_reg));
|
||||
FlushGuestRegister(evict_reg, true, true);
|
||||
|
||||
return HasFreeHostRegister();
|
||||
}
|
||||
|
||||
void RegisterCache::ClearRegisterFromOrder(Reg reg)
|
||||
{
|
||||
for (u32 i = 0; i < m_guest_register_order_count; i++)
|
||||
{
|
||||
if (m_guest_register_order[i] == reg)
|
||||
{
|
||||
// move the registers after backwards into this spot
|
||||
const u32 count_after = m_guest_register_order_count - i - 1;
|
||||
if (count_after > 0)
|
||||
std::memmove(&m_guest_register_order[i], &m_guest_register_order[i + 1], sizeof(Reg) * count_after);
|
||||
else
|
||||
m_guest_register_order[i] = Reg::count;
|
||||
|
||||
m_guest_register_order_count--;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Panic("Clearing register from order not in order");
|
||||
}
|
||||
|
||||
void RegisterCache::PushRegisterToOrder(Reg reg)
|
||||
{
|
||||
for (u32 i = 0; i < m_guest_register_order_count; i++)
|
||||
{
|
||||
if (m_guest_register_order[i] == reg)
|
||||
{
|
||||
// move the registers after backwards into this spot
|
||||
const u32 count_before = i;
|
||||
if (count_before > 0)
|
||||
std::memmove(&m_guest_register_order[1], &m_guest_register_order[0], sizeof(Reg) * count_before);
|
||||
|
||||
m_guest_register_order[0] = reg;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Panic("Attempt to push register which is not ordered");
|
||||
}
|
||||
|
||||
void RegisterCache::AppendRegisterToOrder(Reg reg)
|
||||
{
|
||||
DebugAssert(m_guest_register_order_count < HostReg_Count);
|
||||
if (m_guest_register_order_count > 0)
|
||||
std::memmove(&m_guest_register_order[1], &m_guest_register_order[0], sizeof(Reg) * m_guest_register_order_count);
|
||||
m_guest_register_order[0] = reg;
|
||||
m_guest_register_order_count++;
|
||||
}
|
||||
|
||||
} // namespace CPU::Recompiler
|
||||
@ -0,0 +1,243 @@
|
||||
#pragma once
|
||||
#include "YBaseLib/Assert.h"
|
||||
#include "cpu_recompiler_types.h"
|
||||
#include "cpu_types.h"
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <optional>
|
||||
|
||||
namespace CPU::Recompiler {
|
||||
|
||||
enum class HostRegState : u8
|
||||
{
|
||||
None = 0,
|
||||
Usable = (1 << 1), // Can be allocated
|
||||
CallerSaved = (1 << 2), // Register is caller-saved, and should be saved/restored after calling a function.
|
||||
CalleeSaved = (1 << 3), // Register is callee-saved, and should be restored after leaving the block.
|
||||
InUse = (1 << 4), // In-use, must be saved/restored across function call.
|
||||
CalleeSavedAllocated = (1 << 5), // Register was callee-saved and allocated, so should be restored before returning.
|
||||
Discarded = (1 << 6), // Register contents is not used, so do not preserve across function calls.
|
||||
};
|
||||
IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(HostRegState);
|
||||
|
||||
enum class ValueFlags : u8
|
||||
{
|
||||
None = 0,
|
||||
Valid = (1 << 0),
|
||||
Constant = (1 << 1), // The value itself is constant, and not in a register.
|
||||
InHostRegister = (1 << 2), // The value itself is located in a host register.
|
||||
Scratch = (1 << 3), // The value is temporary, and will be released after the Value is destroyed.
|
||||
Dirty = (1 << 4), // For register cache values, the value needs to be written back to the CPU struct.
|
||||
};
|
||||
IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(ValueFlags);
|
||||
|
||||
struct Value
|
||||
{
|
||||
RegisterCache* regcache = nullptr;
|
||||
u64 constant_value = 0;
|
||||
HostReg host_reg = {};
|
||||
|
||||
RegSize size = RegSize_8;
|
||||
ValueFlags flags = ValueFlags::None;
|
||||
|
||||
Value();
|
||||
Value(RegisterCache* regcache_, u64 constant_, RegSize size_, ValueFlags flags_);
|
||||
Value(RegisterCache* regcache_, HostReg reg_, RegSize size_, ValueFlags flags_);
|
||||
Value(const Value& other);
|
||||
Value(Value&& other);
|
||||
~Value();
|
||||
|
||||
Value& operator=(const Value& other);
|
||||
Value& operator=(Value&& other);
|
||||
|
||||
bool IsConstant() const { return (flags & ValueFlags::Constant) != ValueFlags::None; }
|
||||
bool IsValid() const { return (flags & ValueFlags::Valid) != ValueFlags::None; }
|
||||
bool IsInHostRegister() const { return (flags & ValueFlags::InHostRegister) != ValueFlags::None; }
|
||||
bool IsScratch() const { return (flags & ValueFlags::Scratch) != ValueFlags::None; }
|
||||
|
||||
/// Returns the host register this value is bound to.
|
||||
HostReg GetHostRegister() const
|
||||
{
|
||||
DebugAssert(IsInHostRegister());
|
||||
return host_reg;
|
||||
}
|
||||
|
||||
/// Returns true if this value is constant and has the specified value.
|
||||
bool HasConstantValue(u64 cv) const
|
||||
{
|
||||
return (((flags & ValueFlags::Constant) != ValueFlags::None) && constant_value == cv);
|
||||
}
|
||||
|
||||
/// Removes the contents of this value. Use with care, as scratch/temporaries are not released.
|
||||
void Clear();
|
||||
|
||||
/// Releases the host register if needed, and clears the contents.
|
||||
void ReleaseAndClear();
|
||||
|
||||
/// Flags the value is being discarded. Call Undiscard() to track again.
|
||||
void Discard();
|
||||
void Undiscard();
|
||||
|
||||
void AddHostReg(RegisterCache* regcache_, HostReg hr)
|
||||
{
|
||||
DebugAssert(IsValid());
|
||||
regcache = regcache_;
|
||||
host_reg = hr;
|
||||
flags |= ValueFlags::InHostRegister;
|
||||
}
|
||||
|
||||
void SetHostReg(RegisterCache* regcache_, HostReg hr, RegSize size_)
|
||||
{
|
||||
regcache = regcache_;
|
||||
constant_value = 0;
|
||||
host_reg = hr;
|
||||
size = size_;
|
||||
flags = ValueFlags::Valid | ValueFlags::InHostRegister;
|
||||
}
|
||||
|
||||
void ClearConstant()
|
||||
{
|
||||
// By clearing the constant bit, we should already be in a host register.
|
||||
DebugAssert(IsInHostRegister());
|
||||
flags &= ~ValueFlags::Constant;
|
||||
}
|
||||
|
||||
bool IsDirty() const { return (flags & ValueFlags::Dirty) != ValueFlags::None; }
|
||||
void SetDirty() { flags |= ValueFlags::Dirty; }
|
||||
void ClearDirty() { flags &= ~ValueFlags::Dirty; }
|
||||
|
||||
static Value FromHostReg(RegisterCache* regcache, HostReg reg, RegSize size)
|
||||
{
|
||||
return Value(regcache, reg, size, ValueFlags::Valid | ValueFlags::InHostRegister);
|
||||
}
|
||||
static Value FromScratch(RegisterCache* regcache, HostReg reg, RegSize size)
|
||||
{
|
||||
return Value(regcache, reg, size, ValueFlags::Valid | ValueFlags::InHostRegister | ValueFlags::Scratch);
|
||||
}
|
||||
static Value FromConstant(u64 cv, RegSize size)
|
||||
{
|
||||
return Value(nullptr, cv, size, ValueFlags::Valid | ValueFlags::Constant);
|
||||
}
|
||||
static Value FromConstantU8(u8 value) { return FromConstant(ZeroExtend64(value), RegSize_8); }
|
||||
static Value FromConstantU16(u16 value) { return FromConstant(ZeroExtend64(value), RegSize_16); }
|
||||
static Value FromConstantU32(u32 value) { return FromConstant(ZeroExtend64(value), RegSize_32); }
|
||||
static Value FromConstantU64(u64 value) { return FromConstant(value, RegSize_64); }
|
||||
|
||||
private:
|
||||
void Release();
|
||||
};
|
||||
|
||||
class RegisterCache
|
||||
{
|
||||
public:
|
||||
RegisterCache(CodeGenerator& code_generator);
|
||||
~RegisterCache();
|
||||
|
||||
u32 GetActiveCalleeSavedRegisterCount() const { return m_host_register_callee_saved_order_count; }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Register Allocation
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void SetHostRegAllocationOrder(std::initializer_list<HostReg> regs);
|
||||
void SetCallerSavedHostRegs(std::initializer_list<HostReg> regs);
|
||||
void SetCalleeSavedHostRegs(std::initializer_list<HostReg> regs);
|
||||
void SetCPUPtrHostReg(HostReg reg);
|
||||
|
||||
/// Returns true if the register is permitted to be used in the register cache.
|
||||
bool IsUsableHostReg(HostReg reg) const;
|
||||
bool IsHostRegInUse(HostReg reg) const;
|
||||
bool HasFreeHostRegister() const;
|
||||
u32 GetUsedHostRegisters() const;
|
||||
u32 GetFreeHostRegisters() const;
|
||||
|
||||
/// Allocates a new host register. If there are no free registers, the guest register which was accessed the longest
|
||||
/// time ago will be evicted.
|
||||
HostReg AllocateHostReg(HostRegState state = HostRegState::InUse);
|
||||
|
||||
/// Allocates a specific host register. If this register is not free, returns false.
|
||||
bool AllocateHostReg(HostReg reg, HostRegState state = HostRegState::InUse);
|
||||
|
||||
/// Flags the host register as discard-able. This means that the contents is no longer required, and will not be
|
||||
/// pushed when saving caller-saved registers.
|
||||
void DiscardHostReg(HostReg reg);
|
||||
|
||||
/// Clears the discard-able flag on a host register, so that the contents will be preserved across function calls.
|
||||
void UndiscardHostReg(HostReg reg);
|
||||
|
||||
/// Frees a host register, making it usable in future allocations.
|
||||
void FreeHostReg(HostReg reg);
|
||||
|
||||
/// Ensures a host register is free, removing any value cached.
|
||||
void EnsureHostRegFree(HostReg reg);
|
||||
|
||||
/// Push/pop volatile host registers. Returns the number of registers pushed/popped.
|
||||
u32 PushCallerSavedRegisters() const;
|
||||
u32 PopCallerSavedRegisters() const;
|
||||
|
||||
/// Restore callee-saved registers. Call at the end of the function.
|
||||
u32 PopCalleeSavedRegisters();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Scratch Register Allocation
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
Value GetCPUPtr();
|
||||
Value AllocateScratch(RegSize size, HostReg reg = HostReg_Invalid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Guest Register Caching
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// Returns true if the specified guest register is cached.
|
||||
bool IsGuestRegisterInHostReg(Reg guest_reg) const
|
||||
{
|
||||
return m_guest_reg_cache[static_cast<u8>(guest_reg)].IsInHostRegister();
|
||||
}
|
||||
|
||||
/// Returns the host register if the guest register is cached.
|
||||
std::optional<HostReg> GetHostRegisterForGuestRegister(Reg guest_reg) const
|
||||
{
|
||||
if (!m_guest_reg_cache[static_cast<u8>(guest_reg)].IsInHostRegister())
|
||||
return std::nullopt;
|
||||
return m_guest_reg_cache[static_cast<u8>(guest_reg)].GetHostRegister();
|
||||
}
|
||||
|
||||
Value ReadGuestRegister(Reg guest_reg, bool cache = true, bool force_host_register = false,
|
||||
HostReg forced_host_reg = HostReg_Invalid);
|
||||
|
||||
/// Creates a copy of value, and stores it to guest_reg.
|
||||
Value WriteGuestRegister(Reg guest_reg, Value&& value);
|
||||
|
||||
void FlushGuestRegister(Reg guest_reg, bool invalidate, bool clear_dirty);
|
||||
void InvalidateGuestRegister(Reg guest_reg);
|
||||
|
||||
void FlushAllGuestRegisters(bool invalidate, bool clear_dirty);
|
||||
bool EvictOneGuestRegister();
|
||||
|
||||
private:
|
||||
Value ReadGuestRegister(Value& cache_value, Reg guest_reg, bool cache, bool force_host_register,
|
||||
HostReg forced_host_reg);
|
||||
Value WriteGuestRegister(Value& cache_value, Reg guest_reg, Value&& value);
|
||||
void FlushGuestRegister(Value& cache_value, Reg guest_reg, bool invalidate, bool clear_dirty);
|
||||
void InvalidateGuestRegister(Value& cache_value, Reg guest_reg);
|
||||
void ClearRegisterFromOrder(Reg reg);
|
||||
void PushRegisterToOrder(Reg reg);
|
||||
void AppendRegisterToOrder(Reg reg);
|
||||
|
||||
CodeGenerator& m_code_generator;
|
||||
|
||||
HostReg m_cpu_ptr_host_register = {};
|
||||
std::array<HostRegState, HostReg_Count> m_host_register_state{};
|
||||
std::array<HostReg, HostReg_Count> m_host_register_allocation_order{};
|
||||
u32 m_host_register_available_count = 0;
|
||||
|
||||
std::array<Value, static_cast<u8>(Reg::count)> m_guest_reg_cache{};
|
||||
|
||||
std::array<Reg, HostReg_Count> m_guest_register_order{};
|
||||
u32 m_guest_register_order_count = 0;
|
||||
|
||||
std::array<HostReg, HostReg_Count> m_host_register_callee_saved_order{};
|
||||
u32 m_host_register_callee_saved_order_count = 0;
|
||||
};
|
||||
|
||||
} // namespace CPU::Recompiler
|
||||
@ -0,0 +1,43 @@
|
||||
#include "cpu_recompiler_thunks.h"
|
||||
|
||||
namespace CPU::Recompiler {
|
||||
|
||||
// TODO: Port thunks to "ASM routines", i.e. code in the jit buffer.
|
||||
|
||||
bool Thunks::ReadMemoryByte(Core* cpu, u32 address, u8* value)
|
||||
{
|
||||
return cpu->ReadMemoryByte(address, value);
|
||||
}
|
||||
|
||||
bool Thunks::ReadMemoryHalfWord(Core* cpu, u32 address, u16* value)
|
||||
{
|
||||
return cpu->ReadMemoryHalfWord(address, value);
|
||||
}
|
||||
|
||||
bool Thunks::ReadMemoryWord(Core* cpu, u32 address, u32* value)
|
||||
{
|
||||
return cpu->ReadMemoryWord(address, value);
|
||||
}
|
||||
|
||||
bool Thunks::WriteMemoryByte(Core* cpu, u32 address, u8 value)
|
||||
{
|
||||
return cpu->WriteMemoryByte(address, value);
|
||||
}
|
||||
|
||||
bool Thunks::WriteMemoryHalfWord(Core* cpu, u32 address, u16 value)
|
||||
{
|
||||
return cpu->WriteMemoryHalfWord(address, value);
|
||||
}
|
||||
|
||||
bool Thunks::WriteMemoryWord(Core* cpu, u32 address, u32 value)
|
||||
{
|
||||
return cpu->WriteMemoryWord(address, value);
|
||||
}
|
||||
|
||||
bool Thunks::InterpretInstruction(Core* cpu)
|
||||
{
|
||||
cpu->ExecuteInstruction();
|
||||
return cpu->m_exception_raised;
|
||||
}
|
||||
|
||||
} // namespace CPU::Recompiler
|
||||
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "common/jit_code_buffer.h"
|
||||
#include "cpu_core.h"
|
||||
#include <array>
|
||||
|
||||
namespace CPU::Recompiler {
|
||||
|
||||
class Thunks
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Trampolines for calling back from the JIT
|
||||
// Needed because we can't cast member functions to void*...
|
||||
// TODO: Abuse carry flag or something else for exception
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
static bool ReadMemoryByte(Core* cpu, u32 address, u8* value);
|
||||
static bool ReadMemoryHalfWord(Core* cpu, u32 address, u16* value);
|
||||
static bool ReadMemoryWord(Core* cpu, u32 address, u32* value);
|
||||
static bool WriteMemoryByte(Core* cpu, u32 address, u8 value);
|
||||
static bool WriteMemoryHalfWord(Core* cpu, u32 address, u16 value);
|
||||
static bool WriteMemoryWord(Core* cpu, u32 address, u32 value);
|
||||
static bool InterpretInstruction(Core* cpu);
|
||||
};
|
||||
|
||||
class ASMFunctions
|
||||
{
|
||||
public:
|
||||
bool (*read_memory_byte)(u32 address, u8* value);
|
||||
bool (*read_memory_word)(u32 address, u16* value);
|
||||
bool (*read_memory_dword)(u32 address, u32* value);
|
||||
void (*write_memory_byte)(u32 address, u8 value);
|
||||
void (*write_memory_word)(u32 address, u16 value);
|
||||
void (*write_memory_dword)(u32 address, u32 value);
|
||||
|
||||
void Generate(JitCodeBuffer* code_buffer);
|
||||
};
|
||||
|
||||
} // namespace CPU_X86::Recompiler
|
||||
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
#include "cpu_types.h"
|
||||
|
||||
#if defined(Y_CPU_X64)
|
||||
#define XBYAK_NO_OP_NAMES 1
|
||||
#include "xbyak.h"
|
||||
#endif
|
||||
|
||||
namespace CPU {
|
||||
|
||||
class Core;
|
||||
class CodeCache;
|
||||
|
||||
namespace Recompiler {
|
||||
|
||||
class CodeGenerator;
|
||||
class RegisterCache;
|
||||
|
||||
enum RegSize : u8
|
||||
{
|
||||
RegSize_8,
|
||||
RegSize_16,
|
||||
RegSize_32,
|
||||
RegSize_64,
|
||||
};
|
||||
|
||||
#if defined(Y_CPU_X64)
|
||||
using HostReg = Xbyak::Operand::Code;
|
||||
using CodeEmitter = Xbyak::CodeGenerator;
|
||||
enum : u32
|
||||
{
|
||||
HostReg_Count = 16
|
||||
};
|
||||
constexpr HostReg HostReg_Invalid = static_cast<HostReg>(HostReg_Count);
|
||||
constexpr RegSize HostPointerSize = RegSize_64;
|
||||
|
||||
// A reasonable "maximum" number of bytes per instruction.
|
||||
constexpr u32 MAX_HOST_BYTES_PER_INSTRUCTION = 128;
|
||||
|
||||
#else
|
||||
using HostReg = void;
|
||||
using CodeEmitter = void;
|
||||
enum : u32
|
||||
{
|
||||
HostReg_Count = 0
|
||||
};
|
||||
constexpr HostReg HostReg_Invalid = static_cast<HostReg>(HostReg_Count);
|
||||
constexpr OperandSize HostPointerSize = OperandSize_64;
|
||||
#endif
|
||||
|
||||
} // namespace Recompiler
|
||||
|
||||
} // namespace CPU
|
||||
@ -0,0 +1,196 @@
|
||||
#include "cpu_types.h"
|
||||
#include "YBaseLib/Assert.h"
|
||||
#include <array>
|
||||
|
||||
namespace CPU {
|
||||
static const std::array<const char*, 32> s_reg_names = {
|
||||
{"$zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7",
|
||||
"s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t8", "t9", "k0", "k1", "gp", "sp", "fp", "ra"}};
|
||||
|
||||
const char* GetRegName(Reg reg)
|
||||
{
|
||||
DebugAssert(reg < Reg::count);
|
||||
return s_reg_names[static_cast<u8>(reg)];
|
||||
}
|
||||
|
||||
bool IsBranchInstruction(const Instruction& instruction)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
case InstructionOp::j:
|
||||
case InstructionOp::jal:
|
||||
case InstructionOp::b:
|
||||
case InstructionOp::beq:
|
||||
case InstructionOp::bgtz:
|
||||
case InstructionOp::blez:
|
||||
case InstructionOp::bne:
|
||||
return true;
|
||||
|
||||
case InstructionOp::funct:
|
||||
{
|
||||
switch (instruction.r.funct)
|
||||
{
|
||||
case InstructionFunct::jr:
|
||||
case InstructionFunct::jalr:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsExitBlockInstruction(const Instruction& instruction)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
case InstructionOp::funct:
|
||||
{
|
||||
switch (instruction.r.funct)
|
||||
{
|
||||
case InstructionFunct::syscall:
|
||||
case InstructionFunct::break_:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CanInstructionTrap(const Instruction& instruction, bool in_user_mode)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
case InstructionOp::lui:
|
||||
case InstructionOp::andi:
|
||||
case InstructionOp::ori:
|
||||
case InstructionOp::xori:
|
||||
case InstructionOp::addiu:
|
||||
case InstructionOp::slti:
|
||||
case InstructionOp::sltiu:
|
||||
return false;
|
||||
|
||||
case InstructionOp::cop0:
|
||||
case InstructionOp::cop2:
|
||||
case InstructionOp::lwc2:
|
||||
case InstructionOp::swc2:
|
||||
return in_user_mode;
|
||||
|
||||
// swc0/lwc0/cop1/cop3 are essentially no-ops
|
||||
case InstructionOp::cop1:
|
||||
case InstructionOp::cop3:
|
||||
case InstructionOp::lwc0:
|
||||
case InstructionOp::lwc1:
|
||||
case InstructionOp::lwc3:
|
||||
case InstructionOp::swc0:
|
||||
case InstructionOp::swc1:
|
||||
case InstructionOp::swc3:
|
||||
return false;
|
||||
|
||||
case InstructionOp::addi:
|
||||
case InstructionOp::lb:
|
||||
case InstructionOp::lh:
|
||||
case InstructionOp::lw:
|
||||
case InstructionOp::lbu:
|
||||
case InstructionOp::lhu:
|
||||
case InstructionOp::lwl:
|
||||
case InstructionOp::lwr:
|
||||
case InstructionOp::sb:
|
||||
case InstructionOp::sh:
|
||||
case InstructionOp::sw:
|
||||
case InstructionOp::swl:
|
||||
case InstructionOp::swr:
|
||||
return true;
|
||||
|
||||
// These can fault on the branch address. Perhaps we should move this to the next instruction?
|
||||
case InstructionOp::j:
|
||||
case InstructionOp::jal:
|
||||
case InstructionOp::b:
|
||||
case InstructionOp::beq:
|
||||
case InstructionOp::bgtz:
|
||||
case InstructionOp::blez:
|
||||
case InstructionOp::bne:
|
||||
return true;
|
||||
|
||||
case InstructionOp::funct:
|
||||
{
|
||||
switch (instruction.r.funct)
|
||||
{
|
||||
case InstructionFunct::sll:
|
||||
case InstructionFunct::srl:
|
||||
case InstructionFunct::sra:
|
||||
case InstructionFunct::sllv:
|
||||
case InstructionFunct::srlv:
|
||||
case InstructionFunct::srav:
|
||||
case InstructionFunct::and_:
|
||||
case InstructionFunct::or_:
|
||||
case InstructionFunct::xor_:
|
||||
case InstructionFunct::nor:
|
||||
case InstructionFunct::addu:
|
||||
case InstructionFunct::subu:
|
||||
case InstructionFunct::slt:
|
||||
case InstructionFunct::sltu:
|
||||
case InstructionFunct::mfhi:
|
||||
case InstructionFunct::mthi:
|
||||
case InstructionFunct::mflo:
|
||||
case InstructionFunct::mtlo:
|
||||
case InstructionFunct::mult:
|
||||
case InstructionFunct::multu:
|
||||
case InstructionFunct::div:
|
||||
case InstructionFunct::divu:
|
||||
return false;
|
||||
|
||||
case InstructionFunct::jr:
|
||||
case InstructionFunct::jalr:
|
||||
return true;
|
||||
|
||||
case InstructionFunct::add:
|
||||
case InstructionFunct::sub:
|
||||
case InstructionFunct::syscall:
|
||||
case InstructionFunct::break_:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace CPU
|
||||
Loading…
Reference in New Issue