Interpreter: Handle paired-single HID2.PSE and HID2.LSQE bits

These bits enable or disable paired-single execution based on how
they're set. If PSE isn't set, then all paired-single instructions are
illegal. If PSE is set, but LSQE isn't set, then psq_l, psq_lu, psq_st
and psq_stu are illegal to execute.

Also thanks go out to my roommate @Veegie for letting me use his Wii as
a blasting ground for tests, since mine isn't on hand right now. It only
caught on fire twice and only burned down half of the house through the
process; what a team player.
This commit is contained in:
Lioncash 2018-12-13 11:55:11 -05:00
parent c87a2f57b4
commit 52cae18b01
3 changed files with 100 additions and 55 deletions

View File

@ -20,6 +20,7 @@
#include "Core/HLE/HLE.h"
#include "Core/HW/CPU.h"
#include "Core/Host.h"
#include "Core/PowerPC/Interpreter/ExceptionUtils.h"
#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PPCTables.h"
#include "Core/PowerPC/PowerPC.h"
@ -43,6 +44,42 @@ std::array<Interpreter::Instruction, 1024> Interpreter::m_op_table31;
std::array<Interpreter::Instruction, 32> Interpreter::m_op_table59;
std::array<Interpreter::Instruction, 1024> Interpreter::m_op_table63;
namespace
{
// Determines whether or not the given instruction is one where its execution
// validity is determined by whether or not HID2's LSQE bit is set.
// In other words, if the instruction is psq_l, psq_lu, psq_st, or psq_stu
bool IsPairedSingleQuantizedNonIndexedInstruction(UGeckoInstruction inst)
{
const u32 opcode = inst.OPCD;
return opcode == 0x38 || opcode == 0x39 || opcode == 0x3C || opcode == 0x3D;
}
bool IsPairedSingleInstruction(UGeckoInstruction inst)
{
return inst.OPCD == 4 || IsPairedSingleQuantizedNonIndexedInstruction(inst);
}
// Checks if a given instruction would be illegal to execute if it's a paired single instruction.
//
// Paired single instructions are illegal to execute if HID2.PSE is not set.
// It's also illegal to execute psq_l, psq_lu, psq_st, and psq_stu if HID2.PSE is enabled,
// but HID2.LSQE is not set.
bool IsInvalidPairedSingleExecution(UGeckoInstruction inst)
{
if (!HID2.PSE && IsPairedSingleInstruction(inst))
return true;
return HID2.PSE && !HID2.LSQE && IsPairedSingleQuantizedNonIndexedInstruction(inst);
}
void UpdatePC()
{
last_pc = PC;
PC = NPC;
}
} // Anonymous namespace
void Interpreter::RunTable4(UGeckoInstruction inst)
{
m_op_table4[inst.SUBOP10](inst);
@ -110,75 +147,77 @@ bool Interpreter::HandleFunctionHooking(u32 address)
int Interpreter::SingleStepInner()
{
if (!HandleFunctionHooking(PC))
if (HandleFunctionHooking(PC))
{
#ifdef USE_GDBSTUB
if (gdb_active() && gdb_bp_x(PC))
{
Host_UpdateDisasmDialog();
UpdatePC();
return PPCTables::GetOpInfo(m_prev_inst)->numCycles;
}
gdb_signal(GDB_SIGTRAP);
gdb_handle_exception();
}
#ifdef USE_GDBSTUB
if (gdb_active() && gdb_bp_x(PC))
{
Host_UpdateDisasmDialog();
gdb_signal(GDB_SIGTRAP);
gdb_handle_exception();
}
#endif
NPC = PC + sizeof(UGeckoInstruction);
m_prev_inst.hex = PowerPC::Read_Opcode(PC);
NPC = PC + sizeof(UGeckoInstruction);
m_prev_inst.hex = PowerPC::Read_Opcode(PC);
// Uncomment to trace the interpreter
// if ((PC & 0xffffff)>=0x0ab54c && (PC & 0xffffff)<=0x0ab624)
// startTrace = 1;
// else
// startTrace = 0;
// Uncomment to trace the interpreter
// if ((PC & 0xffffff)>=0x0ab54c && (PC & 0xffffff)<=0x0ab624)
// startTrace = 1;
// else
// startTrace = 0;
if (startTrace)
if (startTrace)
{
Trace(m_prev_inst);
}
if (m_prev_inst.hex != 0)
{
if (IsInvalidPairedSingleExecution(m_prev_inst))
{
Trace(m_prev_inst);
GenerateProgramException();
CheckExceptions();
}
if (m_prev_inst.hex != 0)
else if (MSR.FP)
{
if (MSR.FP) // If FPU is enabled, just execute
m_op_table[m_prev_inst.OPCD](m_prev_inst);
if (PowerPC::ppcState.Exceptions & EXCEPTION_DSI)
{
m_op_table[m_prev_inst.OPCD](m_prev_inst);
if (PowerPC::ppcState.Exceptions & EXCEPTION_DSI)
{
PowerPC::CheckExceptions();
m_end_block = true;
}
}
else
{
// check if we have to generate a FPU unavailable exception
if (!PPCTables::UsesFPU(m_prev_inst))
{
m_op_table[m_prev_inst.OPCD](m_prev_inst);
if (PowerPC::ppcState.Exceptions & EXCEPTION_DSI)
{
PowerPC::CheckExceptions();
m_end_block = true;
}
}
else
{
PowerPC::ppcState.Exceptions |= EXCEPTION_FPU_UNAVAILABLE;
PowerPC::CheckExceptions();
m_end_block = true;
}
CheckExceptions();
}
}
else
{
// Memory exception on instruction fetch
PowerPC::CheckExceptions();
m_end_block = true;
// check if we have to generate a FPU unavailable exception or a program exception.
if (PPCTables::UsesFPU(m_prev_inst))
{
PowerPC::ppcState.Exceptions |= EXCEPTION_FPU_UNAVAILABLE;
CheckExceptions();
}
else
{
m_op_table[m_prev_inst.OPCD](m_prev_inst);
if (PowerPC::ppcState.Exceptions & EXCEPTION_DSI)
{
CheckExceptions();
}
}
}
}
last_pc = PC;
PC = NPC;
else
{
// Memory exception on instruction fetch
CheckExceptions();
}
const GekkoOPInfo* opinfo = PPCTables::GetOpInfo(m_prev_inst);
return opinfo->numCycles;
UpdatePC();
return PPCTables::GetOpInfo(m_prev_inst)->numCycles;
}
void Interpreter::SingleStep()
@ -317,6 +356,12 @@ void Interpreter::ClearCache()
// Do nothing.
}
void Interpreter::CheckExceptions()
{
PowerPC::CheckExceptions();
m_end_block = true;
}
const char* Interpreter::GetName() const
{
#ifdef _ARCH_64

View File

@ -282,6 +282,8 @@ public:
static u32 Helper_Carry(u32 value1, u32 value2);
private:
void CheckExceptions();
static void InitializeInstructionTables();
static bool HandleFunctionHooking(u32 address);

View File

@ -341,9 +341,7 @@ void Interpreter::mtspr(UGeckoInstruction inst)
rSPR(index) &= 0xF8000000;
break;
case SPR_HID2: // HID2
// TODO: generate illegal instruction for paired inst if PSE or LSQE
// not set.
case SPR_HID2:
// TODO: disable write gather pipe if WPE not set
// TODO: emulate locked cache and DMA bits.
// Only the lower half of the register (upper half from a little endian perspective)