CPU/Recompiler: Break blocks on invalid instructions

This commit is contained in:
Stenzek 2024-07-11 12:29:34 +10:00
parent 3b9c489787
commit 2ac2ad605e
No known key found for this signature in database
9 changed files with 117 additions and 117 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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