Skip to content

Commit

Permalink
CPU/Recompiler: Break blocks on invalid instructions
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek committed Jul 11, 2024
1 parent 3b9c489 commit 2ac2ad6
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 125 deletions.
8 changes: 5 additions & 3 deletions src/core/cpu_code_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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)
{
Expand Down
1 change: 0 additions & 1 deletion src/core/cpu_code_cache_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
2 changes: 2 additions & 0 deletions src/core/cpu_newrec_compiler_aarch32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions src/core/cpu_newrec_compiler_aarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions src/core/cpu_newrec_compiler_riscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/core/cpu_newrec_compiler_x64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
21 changes: 7 additions & 14 deletions src/core/cpu_recompiler_code_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
// 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);

m_current_instruction_in_branch_delay_slot_dirty = info.is_branch_instruction;
m_branch_was_taken_dirty = info.is_branch_instruction;
Expand Down
209 changes: 104 additions & 105 deletions src/core/cpu_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;

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

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 CPU::IsInvalidInstruction(const Instruction instruction)
{
// TODO
return true;
// 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 = {};

#define SET(op) ret[static_cast<size_t>(op) / 32] |= (1u << (static_cast<size_t>(op) % 32));

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

// Invalid COP0-3 ops don't raise #RI?
SET(InstructionOp::cop0);
SET(InstructionOp::cop1);
SET(InstructionOp::cop2);
SET(InstructionOp::cop3);

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

#undef SET

return ret;
}();

static constexpr const std::array<u32, 64 / 32> valid_func_map = []() constexpr {
std::array<u32, 64 / 32> ret = {};

#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
}
3 changes: 1 addition & 2 deletions src/core/cpu_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down

0 comments on commit 2ac2ad6

Please sign in to comment.