mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-20 07:15:38 +00:00
CPU/Recompiler: Break blocks on invalid instructions
This commit is contained in:
parent
3b9c489787
commit
2ac2ad605e
|
@ -936,8 +936,12 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i
|
|||
}
|
||||
|
||||
Instruction instruction;
|
||||
if (!SafeReadInstruction(pc, &instruction.bits) || !IsInvalidInstruction(instruction))
|
||||
if (!SafeReadInstruction(pc, &instruction.bits) || !IsValidInstruction(instruction))
|
||||
{
|
||||
// Away to the int you go!
|
||||
ERROR_LOG("Instruction read failed at PC=0x{:08X}, truncating block.", pc);
|
||||
break;
|
||||
}
|
||||
|
||||
InstructionInfo info;
|
||||
std::memset(&info, 0, sizeof(info));
|
||||
|
@ -951,8 +955,6 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i
|
|||
info.is_load_instruction = IsMemoryLoadInstruction(instruction);
|
||||
info.is_store_instruction = IsMemoryStoreInstruction(instruction);
|
||||
info.has_load_delay = InstructionHasLoadDelay(instruction);
|
||||
info.can_trap = CanInstructionTrap(instruction, false /*InUserMode()*/);
|
||||
info.is_direct_branch_instruction = IsDirectBranchInstruction(instruction);
|
||||
|
||||
if (g_settings.cpu_recompiler_icache)
|
||||
{
|
||||
|
|
|
@ -53,7 +53,6 @@ struct InstructionInfo
|
|||
bool is_load_delay_slot : 1;
|
||||
bool is_last_instruction : 1;
|
||||
bool has_load_delay : 1;
|
||||
bool can_trap : 1;
|
||||
|
||||
u8 reg_flags[static_cast<u8>(Reg::count)];
|
||||
// Reg write_reg[3];
|
||||
|
|
|
@ -721,6 +721,8 @@ void CPU::NewRec::AArch32Compiler::Flush(u32 flags)
|
|||
|
||||
void CPU::NewRec::AArch32Compiler::Compile_Fallback()
|
||||
{
|
||||
WARNING_LOG("Compiling instruction fallback at PC=0x{:08X}, instruction=0x{:08X}", iinfo->pc, inst->bits);
|
||||
|
||||
Flush(FLUSH_FOR_INTERPRETER);
|
||||
|
||||
EmitCall(reinterpret_cast<const void*>(&CPU::Recompiler::Thunks::InterpretInstruction));
|
||||
|
|
|
@ -693,6 +693,8 @@ void CPU::NewRec::AArch64Compiler::Flush(u32 flags)
|
|||
|
||||
void CPU::NewRec::AArch64Compiler::Compile_Fallback()
|
||||
{
|
||||
WARNING_LOG("Compiling instruction fallback at PC=0x{:08X}, instruction=0x{:08X}", iinfo->pc, inst->bits);
|
||||
|
||||
Flush(FLUSH_FOR_INTERPRETER);
|
||||
|
||||
EmitCall(reinterpret_cast<const void*>(&CPU::Recompiler::Thunks::InterpretInstruction));
|
||||
|
|
|
@ -970,6 +970,8 @@ void CPU::NewRec::RISCV64Compiler::Flush(u32 flags)
|
|||
|
||||
void CPU::NewRec::RISCV64Compiler::Compile_Fallback()
|
||||
{
|
||||
WARNING_LOG("Compiling instruction fallback at PC=0x{:08X}, instruction=0x{:08X}", iinfo->pc, inst->bits);
|
||||
|
||||
Flush(FLUSH_FOR_INTERPRETER);
|
||||
|
||||
#if 0
|
||||
|
|
|
@ -610,6 +610,8 @@ void CPU::NewRec::X64Compiler::Flush(u32 flags)
|
|||
|
||||
void CPU::NewRec::X64Compiler::Compile_Fallback()
|
||||
{
|
||||
WARNING_LOG("Compiling instruction fallback at PC=0x{:08X}, instruction=0x{:08X}", iinfo->pc, inst->bits);
|
||||
|
||||
Flush(FLUSH_FOR_INTERPRETER);
|
||||
|
||||
cg->call(&CPU::Recompiler::Thunks::InterpretInstruction);
|
||||
|
|
|
@ -1191,6 +1191,8 @@ void CodeGenerator::WriteNewPC(const Value& value, bool commit)
|
|||
|
||||
bool CodeGenerator::Compile_Fallback(Instruction instruction, const CodeCache::InstructionInfo& info)
|
||||
{
|
||||
WARNING_LOG("Compiling instruction fallback at PC=0x{:08X}, instruction=0x{:08X}", info.pc, instruction.bits);
|
||||
|
||||
InstructionPrologue(instruction, info, 1, true);
|
||||
|
||||
// flush and invalidate all guest registers, since the fallback could change any of them
|
||||
|
@ -1204,20 +1206,11 @@ bool CodeGenerator::Compile_Fallback(Instruction instruction, const CodeCache::I
|
|||
EmitStoreCPUStructField(OFFSETOF(State, current_instruction_pc), Value::FromConstantU32(info.pc));
|
||||
EmitStoreCPUStructField(OFFSETOF(State, current_instruction.bits), Value::FromConstantU32(instruction.bits));
|
||||
|
||||
// emit the function call
|
||||
if (CanInstructionTrap(instruction, false /*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,
|
||||
g_settings.gpu_pgxp_enable ? &Thunks::InterpretInstructionPGXP : &Thunks::InterpretInstruction);
|
||||
EmitExceptionExitOnBool(return_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitFunctionCall(nullptr,
|
||||
g_settings.gpu_pgxp_enable ? &Thunks::InterpretInstructionPGXP : &Thunks::InterpretInstruction);
|
||||
}
|
||||
|
||||
m_current_instruction_in_branch_delay_slot_dirty = info.is_branch_instruction;
|
||||
m_branch_was_taken_dirty = info.is_branch_instruction;
|
||||
|
|
|
@ -186,7 +186,8 @@ bool CPU::IsMemoryStoreInstruction(const Instruction instruction)
|
|||
}
|
||||
}
|
||||
|
||||
std::optional<VirtualMemoryAddress> CPU::GetLoadStoreEffectiveAddress(const Instruction instruction, const Registers* regs)
|
||||
std::optional<VirtualMemoryAddress> CPU::GetLoadStoreEffectiveAddress(const Instruction instruction,
|
||||
const Registers* regs)
|
||||
{
|
||||
switch (instruction.op)
|
||||
{
|
||||
|
@ -265,109 +266,107 @@ bool CPU::IsExitBlockInstruction(const Instruction instruction)
|
|||
}
|
||||
}
|
||||
|
||||
bool CPU::CanInstructionTrap(const Instruction instruction, bool in_user_mode)
|
||||
bool CPU::IsValidInstruction(const Instruction instruction)
|
||||
{
|
||||
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;
|
||||
// No constexpr std::bitset until C++23 :(
|
||||
static constexpr const std::array<u32, 64 / 32> valid_op_map = []() constexpr {
|
||||
std::array<u32, 64 / 32> ret = {};
|
||||
|
||||
case InstructionOp::cop0:
|
||||
case InstructionOp::cop2:
|
||||
case InstructionOp::lwc2:
|
||||
case InstructionOp::swc2:
|
||||
return in_user_mode;
|
||||
#define SET(op) ret[static_cast<size_t>(op) / 32] |= (1u << (static_cast<size_t>(op) % 32));
|
||||
|
||||
// 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;
|
||||
SET(InstructionOp::b);
|
||||
SET(InstructionOp::j);
|
||||
SET(InstructionOp::jal);
|
||||
SET(InstructionOp::beq);
|
||||
SET(InstructionOp::bne);
|
||||
SET(InstructionOp::blez);
|
||||
SET(InstructionOp::bgtz);
|
||||
SET(InstructionOp::addi);
|
||||
SET(InstructionOp::addiu);
|
||||
SET(InstructionOp::slti);
|
||||
SET(InstructionOp::sltiu);
|
||||
SET(InstructionOp::andi);
|
||||
SET(InstructionOp::ori);
|
||||
SET(InstructionOp::xori);
|
||||
SET(InstructionOp::lui);
|
||||
|
||||
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;
|
||||
// Invalid COP0-3 ops don't raise #RI?
|
||||
SET(InstructionOp::cop0);
|
||||
SET(InstructionOp::cop1);
|
||||
SET(InstructionOp::cop2);
|
||||
SET(InstructionOp::cop3);
|
||||
|
||||
// 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 false;
|
||||
SET(InstructionOp::lb);
|
||||
SET(InstructionOp::lh);
|
||||
SET(InstructionOp::lwl);
|
||||
SET(InstructionOp::lw);
|
||||
SET(InstructionOp::lbu);
|
||||
SET(InstructionOp::lhu);
|
||||
SET(InstructionOp::lwr);
|
||||
SET(InstructionOp::sb);
|
||||
SET(InstructionOp::sh);
|
||||
SET(InstructionOp::swl);
|
||||
SET(InstructionOp::sw);
|
||||
SET(InstructionOp::swr);
|
||||
SET(InstructionOp::lwc0);
|
||||
SET(InstructionOp::lwc1);
|
||||
SET(InstructionOp::lwc2);
|
||||
SET(InstructionOp::lwc3);
|
||||
SET(InstructionOp::swc0);
|
||||
SET(InstructionOp::swc1);
|
||||
SET(InstructionOp::swc2);
|
||||
SET(InstructionOp::swc3);
|
||||
|
||||
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;
|
||||
#undef SET
|
||||
|
||||
case InstructionFunct::jr:
|
||||
case InstructionFunct::jalr:
|
||||
return true;
|
||||
return ret;
|
||||
}();
|
||||
|
||||
case InstructionFunct::add:
|
||||
case InstructionFunct::sub:
|
||||
case InstructionFunct::syscall:
|
||||
case InstructionFunct::break_:
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
static constexpr const std::array<u32, 64 / 32> valid_func_map = []() constexpr {
|
||||
std::array<u32, 64 / 32> ret = {};
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CPU::IsInvalidInstruction(const Instruction instruction)
|
||||
{
|
||||
// TODO
|
||||
return true;
|
||||
#define SET(op) ret[static_cast<size_t>(op) / 32] |= (1u << (static_cast<size_t>(op) % 32));
|
||||
|
||||
SET(InstructionFunct::sll);
|
||||
SET(InstructionFunct::srl);
|
||||
SET(InstructionFunct::sra);
|
||||
SET(InstructionFunct::sllv);
|
||||
SET(InstructionFunct::srlv);
|
||||
SET(InstructionFunct::srav);
|
||||
SET(InstructionFunct::jr);
|
||||
SET(InstructionFunct::jalr);
|
||||
SET(InstructionFunct::syscall);
|
||||
SET(InstructionFunct::break_);
|
||||
SET(InstructionFunct::mfhi);
|
||||
SET(InstructionFunct::mthi);
|
||||
SET(InstructionFunct::mflo);
|
||||
SET(InstructionFunct::mtlo);
|
||||
SET(InstructionFunct::mult);
|
||||
SET(InstructionFunct::multu);
|
||||
SET(InstructionFunct::div);
|
||||
SET(InstructionFunct::divu);
|
||||
SET(InstructionFunct::add);
|
||||
SET(InstructionFunct::addu);
|
||||
SET(InstructionFunct::sub);
|
||||
SET(InstructionFunct::subu);
|
||||
SET(InstructionFunct::and_);
|
||||
SET(InstructionFunct::or_);
|
||||
SET(InstructionFunct::xor_);
|
||||
SET(InstructionFunct::nor);
|
||||
SET(InstructionFunct::slt);
|
||||
SET(InstructionFunct::sltu);
|
||||
|
||||
#undef SET
|
||||
|
||||
return ret;
|
||||
}();
|
||||
|
||||
#define CHECK(arr, val) ((arr[static_cast<size_t>(val) / 32] & (1u << (static_cast<size_t>(val) % 32))) != 0u)
|
||||
|
||||
if (instruction.op == InstructionOp::funct)
|
||||
return CHECK(valid_func_map, instruction.r.funct.GetValue());
|
||||
else
|
||||
return CHECK(valid_op_map, instruction.op.GetValue());
|
||||
|
||||
#undef CHECK
|
||||
}
|
||||
|
|
|
@ -231,8 +231,7 @@ 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 IsInvalidInstruction(const Instruction instruction);
|
||||
bool IsValidInstruction(const Instruction instruction);
|
||||
|
||||
struct Registers
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue