#include "cpu_disasm.h"
#include "cpu_core.h"
#include "cpu_types.h"
#include "common/assert.h"
#include "common/small_string.h"
#include <array>
namespace CPU {
namespace {
enum Operand : u8
{
Operand_None,
i_rs,
i_rt,
i_imm,
j_target,
r_rs,
r_rt,
r_rd,
r_shamt,
r_funct
};
struct TableEntry
{
const char* format;
};
struct GTEInstructionTable
{
const char* name;
bool sf;
bool lm;
bool mvmva;
};
}
static void FormatInstruction(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format);
static void FormatComment(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format);
template<typename T>
static void FormatCopInstruction(SmallStringBase* dest, u32 pc, const Instruction inst,
const std::pair<T, const char*>* table, size_t table_size, T table_key);
template<typename T>
static void FormatCopComment(SmallStringBase* dest, u32 pc, const Instruction inst,
const std::pair<T, const char*>* table, size_t table_size, T table_key);
static void FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction inst);
static const std::array<const char*, 64> s_base_table = {{
"",
"UNKNOWN",
"j $jt",
"jal $jt",
"beq $rs, $rt, $rel",
"bne $rs, $rt, $rel",
"blez $rs, $rel",
"bgtz $rs, $rel",
"addi $rt, $rs, $imm",
"addiu $rt, $rs, $imm",
"slti $rt, $rs, $imm",
"sltiu $rt, $rs, $immu",
"andi $rt, $rs, $immx",
"ori $rt, $rs, $immx",
"xori $rt, $rs, $immx",
"lui $rt, $immx",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"lb $rt, $offsetrs",
"lh $rt, $offsetrs",
"lwl $rt, $offsetrs",
"lw $rt, $offsetrs",
"lbu $rt, $offsetrs",
"lhu $rt, $offsetrs",
"lwr $rt, $offsetrs",
"UNKNOWN",
"sb $rt, $offsetrs",
"sh $rt, $offsetrs",
"swl $rt, $offsetrs",
"sw $rt, $offsetrs",
"UNKNOWN",
"UNKNOWN",
"swr $rt, $offsetrs",
"UNKNOWN",
"lwc0 $coprt, $offsetrs",
"lwc1 $coprt, $offsetrs",
"lwc2 $coprt, $offsetrs",
"lwc3 $coprt, $offsetrs",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"swc0 $coprt, $offsetrs",
"swc1 $coprt, $offsetrs",
"swc2 $coprt, $offsetrs",
"swc3 $coprt, $offsetrs",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN"
}};
static const std::array<const char*, 64> s_special_table = {{
"sll $rd, $rt, $shamt",
"UNKNOWN",
"srl $rd, $rt, $shamt",
"sra $rd, $rt, $shamt",
"sllv $rd, $rt, $rs",
"UNKNOWN",
"srlv $rd, $rt, $rs",
"srav $rd, $rt, $rs",
"jr $rs",
"jalr $rd, $rs",
"UNKNOWN",
"UNKNOWN",
"syscall",
"break",
"UNKNOWN",
"UNKNOWN",
"mfhi $rd",
"mthi $rs",
"mflo $rd",
"mtlo $rs",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"mult $rs, $rt",
"multu $rs, $rt",
"div $rs, $rt",
"divu $rs, $rt",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"add $rd, $rs, $rt",
"addu $rd, $rs, $rt",
"sub $rd, $rs, $rt",
"subu $rd, $rs, $rt",
"and $rd, $rs, $rt",
"or $rd, $rs, $rt",
"xor $rd, $rs, $rt",
"nor $rd, $rs, $rt",
"UNKNOWN",
"UNKNOWN",
"slt $rd, $rs, $rt",
"sltu $rd, $rs, $rt",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN",
"UNKNOWN"
}};
static const std::array<std::pair<CopCommonInstruction, const char*>, 4> s_cop_common_table = {
{{CopCommonInstruction::mfcn, "mfc$cop $rt_, $coprd"},
{CopCommonInstruction::cfcn, "cfc$cop $rt_, $coprdc"},
{CopCommonInstruction::mtcn, "mtc$cop $rt, $coprd"},
{CopCommonInstruction::ctcn, "ctc$cop $rt, $coprdc"}}};
static const std::array<std::pair<Cop0Instruction, const char*>, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}};
static constexpr const std::array<const char*, 64> s_gte_register_names = {
{"v0_xy", "v0_z", "v1_xy", "v1_z", "v2_xy", "v2_z", "rgbc", "otz", "ir0", "ir1", "ir2", "ir3", "sxy0",
"sxy1", "sxy2", "sxyp", "sz0", "sz1", "sz2", "sz3", "rgb0", "rgb1", "rgb2", "res1", "mac0", "mac1",
"mac2", "mac3", "irgb", "orgb", "lzcs", "lzcr", "rt_0", "rt_1", "rt_2", "rt_3", "rt_4", "trx", "try",
"trz", "llm_0", "llm_1", "llm_2", "llm_3", "llm_4", "rbk", "gbk", "bbk", "lcm_0", "lcm_1", "lcm_2", "lcm_3",
"lcm_4", "rfc", "gfc", "bfc", "ofx", "ofy", "h", "dqa", "dqb", "zsf3", "zsf4", "flag"}};
static constexpr const std::array<GTEInstructionTable, 64> s_gte_instructions = {{
{"UNKNOWN", false, false, false},
{"rtps", true, true, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"nclip", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"op", true, true, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"dpcs", true, true, false},
{"intpl", true, true, false},
{"mvmva", true, true, true},
{"ncds", true, true, false},
{"cdp", true, true, false},
{"UNKNOWN", false, false, false},
{"ncdt", true, true, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"nccs", true, true, false},
{"cc", true, true, false},
{"UNKNOWN", false, false, false},
{"ncs", true, true, false},
{"UNKNOWN", false, false, false},
{"nct", true, true, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"sqr", true, true, false},
{"dcpl", true, true, false},
{"dpct", true, true, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"avsz3", false, false, false},
{"avsz4", false, false, false},
{"UNKNOWN", false, false, false},
{"rtpt", true, true, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"UNKNOWN", false, false, false},
{"gpf", true, true, false},
{"gpl", true, true, false},
{"ncct", true, true, false},
}};
}
void CPU::FormatInstruction(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format)
{
dest->clear();
const char* str = format;
while (*str != '\0')
{
const char ch = *(str++);
if (ch != '$')
{
dest->append(ch);
continue;
}
if (std::strncmp(str, "rs", 2) == 0)
{
dest->append(GetRegName(inst.r.rs));
str += 2;
}
else if (std::strncmp(str, "rt_", 3) == 0)
{
dest->append(GetRegName(inst.r.rt));
str += 3;
}
else if (std::strncmp(str, "rt", 2) == 0)
{
dest->append(GetRegName(inst.r.rt));
str += 2;
}
else if (std::strncmp(str, "rd", 2) == 0)
{
dest->append(GetRegName(inst.r.rd));
str += 2;
}
else if (std::strncmp(str, "shamt", 5) == 0)
{
dest->append_format("{}", ZeroExtend32(inst.r.shamt.GetValue()));
str += 5;
}
else if (std::strncmp(str, "immu", 4) == 0)
{
dest->append_format("{}", inst.i.imm_zext32());
str += 4;
}
else if (std::strncmp(str, "immx", 4) == 0)
{
dest->append_format("0x{:04x}", inst.i.imm_zext32());
str += 4;
}
else if (std::strncmp(str, "imm", 3) == 0)
{
dest->append_format("{}", static_cast<s32>(inst.i.imm_sext32()));
str += 3;
}
else if (std::strncmp(str, "rel", 3) == 0)
{
const u32 target = (pc + UINT32_C(4)) + (inst.i.imm_sext32() << 2);
dest->append_format("0x{:08x}", target);
str += 3;
}
else if (std::strncmp(str, "offsetrs", 8) == 0)
{
const s32 offset = static_cast<s32>(inst.i.imm_sext32());
dest->append_format("{}0x{:x}({})", (offset < 0) ? "-" : "", (offset < 0) ? -offset : offset,
GetRegName(inst.i.rs));
str += 8;
}
else if (std::strncmp(str, "jt", 2) == 0)
{
const u32 target = ((pc + UINT32_C(4)) & UINT32_C(0xF0000000)) | (inst.j.target << 2);
dest->append_format("0x{:08x}", target);
str += 2;
}
else if (std::strncmp(str, "copcc", 5) == 0)
{
dest->append(((inst.bits & (UINT32_C(1) << 24)) != 0) ? 't' : 'f');
str += 5;
}
else if (std::strncmp(str, "coprdc", 6) == 0)
{
if (inst.IsCop2Instruction())
dest->append(GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue()) + 32));
else
dest->append_format("{}", ZeroExtend32(static_cast<u8>(inst.r.rd.GetValue())));
str += 6;
}
else if (std::strncmp(str, "coprd", 5) == 0)
{
if (inst.IsCop2Instruction())
dest->append(GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue())));
else
dest->append_format("{}", ZeroExtend32(static_cast<u8>(inst.r.rd.GetValue())));
str += 5;
}
else if (std::strncmp(str, "coprt", 5) == 0)
{
if (inst.IsCop2Instruction())
dest->append(GetGTERegisterName(static_cast<u8>(inst.r.rt.GetValue())));
else
dest->append_format("{}", ZeroExtend32(static_cast<u8>(inst.r.rt.GetValue())));
str += 5;
}
else if (std::strncmp(str, "cop", 3) == 0)
{
dest->append_format("{}", static_cast<u8>(inst.op.GetValue()) & INSTRUCTION_COP_N_MASK);
str += 3;
}
else
{
Panic("Unknown operand");
}
}
}
void CPU::FormatComment(SmallStringBase* dest, const Instruction inst, u32 pc, const char* format)
{
const CPU::Registers* regs = &CPU::g_state.regs;
const char* str = format;
while (*str != '\0')
{
const char ch = *(str++);
if (ch != '$')
continue;
if (std::strncmp(str, "rs", 2) == 0)
{
dest->append_format("{}{}=0x{:08x}", dest->empty() ? "" : ", ", GetRegName(inst.r.rs),
regs->r[static_cast<u8>(inst.r.rs.GetValue())]);
str += 2;
}
else if (std::strncmp(str, "rt_", 3) == 0)
{
str += 3;
}
else if (std::strncmp(str, "rt", 2) == 0)
{
dest->append_format("{}{}=0x{:08x}", dest->empty() ? "" : ", ", GetRegName(inst.r.rt),
regs->r[static_cast<u8>(inst.r.rt.GetValue())]);
str += 2;
}
else if (std::strncmp(str, "rd", 2) == 0)
{
dest->append_format("{}{}=0x{:08x}", dest->empty() ? "" : ", ", GetRegName(inst.r.rd),
regs->r[static_cast<u8>(inst.r.rd.GetValue())]);
str += 2;
}
else if (std::strncmp(str, "shamt", 5) == 0)
{
str += 5;
}
else if (std::strncmp(str, "immu", 4) == 0)
{
str += 4;
}
else if (std::strncmp(str, "imm", 3) == 0)
{
str += 3;
}
else if (std::strncmp(str, "rel", 3) == 0)
{
str += 3;
}
else if (std::strncmp(str, "offsetrs", 8) == 0)
{
const s32 offset = static_cast<s32>(inst.i.imm_sext32());
const VirtualMemoryAddress address = (regs->r[static_cast<u8>(inst.i.rs.GetValue())] + offset);
if (!dest->empty())
dest->append_format(", ");
if (inst.op == InstructionOp::lb || inst.op == InstructionOp::lbu)
{
u8 data = 0;
CPU::SafeReadMemoryByte(address, &data);
dest->append_format("addr=0x{:08x}[0x{:02x}]", address, data);
}
else if (inst.op == InstructionOp::lh || inst.op == InstructionOp::lhu)
{
u16 data = 0;
CPU::SafeReadMemoryHalfWord(address, &data);
dest->append_format("addr=0x{:08x}[0x{:04x}]", address, data);
}
else if (inst.op == InstructionOp::lw || (inst.op >= InstructionOp::lwc0 && inst.op <= InstructionOp::lwc3) ||
inst.op == InstructionOp::lwl || inst.op == InstructionOp::lwr)
{
u32 data = 0;
CPU::SafeReadMemoryWord(address, &data);
dest->append_format("addr=0x{:08x}[0x{:08x}]", address, data);
}
else
{
dest->append_format("addr=0x{:08x}", address);
}
str += 8;
}
else if (std::strncmp(str, "jt", 2) == 0)
{
str += 2;
}
else if (std::strncmp(str, "copcc", 5) == 0)
{
str += 5;
}
else if (std::strncmp(str, "coprdc", 6) == 0)
{
if (inst.IsCop2Instruction())
{
dest->append_format("{}{}=0x{:08x}", dest->empty() ? "" : ", ",
GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue()) + 32),
g_state.gte_regs.cr32[static_cast<u8>(inst.r.rd.GetValue())]);
}
str += 6;
}
else if (std::strncmp(str, "coprd", 5) == 0)
{
if (inst.IsCop2Instruction())
{
dest->append_format("{}{}=0x{:08x}", dest->empty() ? "" : ", ",
GetGTERegisterName(static_cast<u8>(inst.r.rd.GetValue())),
g_state.gte_regs.dr32[static_cast<u8>(inst.r.rd.GetValue())]);
}
str += 5;
}
else if (std::strncmp(str, "coprt", 5) == 0)
{
if (inst.IsCop2Instruction())
{
dest->append_format("{}{}=0x{:08x}", dest->empty() ? "" : ", ",
GetGTERegisterName(static_cast<u8>(inst.r.rt.GetValue())),
g_state.gte_regs.dr32[static_cast<u8>(inst.r.rt.GetValue())]);
}
str += 5;
}
else if (std::strncmp(str, "cop", 3) == 0)
{
str += 3;
}
else
{
Panic("Unknown operand");
}
}
}
template<typename T>
void CPU::FormatCopInstruction(SmallStringBase* dest, u32 pc, const Instruction inst,
const std::pair<T, const char*>* table, size_t table_size, T table_key)
{
for (size_t i = 0; i < table_size; i++)
{
if (table[i].first == table_key)
{
FormatInstruction(dest, inst, pc, table[i].second);
return;
}
}
dest->format("<cop{} 0x{:08x}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
}
template<typename T>
void CPU::FormatCopComment(SmallStringBase* dest, u32 pc, const Instruction inst,
const std::pair<T, const char*>* table, size_t table_size, T table_key)
{
for (size_t i = 0; i < table_size; i++)
{
if (table[i].first == table_key)
{
FormatComment(dest, inst, pc, table[i].second);
return;
}
}
}
void CPU::FormatGTEInstruction(SmallStringBase* dest, u32 pc, const Instruction inst)
{
const GTE::Instruction gi{inst.bits};
const GTEInstructionTable& t = s_gte_instructions[gi.command];
dest->assign(t.name);
if (t.sf && gi.sf)
dest->append(" sf");
if (t.lm && gi.lm)
dest->append(" lm");
if (t.mvmva)
{
dest->append_format(" m={} v={} t={}", static_cast<u8>(gi.mvmva_multiply_matrix),
static_cast<u8>(gi.mvmva_multiply_vector), static_cast<u8>(gi.mvmva_translation_vector));
}
}
void CPU::DisassembleInstruction(SmallStringBase* dest, u32 pc, u32 bits)
{
const Instruction inst{bits};
switch (inst.op)
{
case InstructionOp::funct:
FormatInstruction(dest, inst, pc, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
return;
case InstructionOp::cop0:
case InstructionOp::cop1:
case InstructionOp::cop2:
case InstructionOp::cop3:
{
if (inst.cop.IsCommonInstruction())
{
FormatCopInstruction(dest, pc, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp());
}
else
{
switch (inst.op)
{
case InstructionOp::cop0:
{
FormatCopInstruction(dest, pc, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
}
break;
case InstructionOp::cop2:
{
FormatGTEInstruction(dest, pc, inst);
}
break;
case InstructionOp::cop1:
case InstructionOp::cop3:
default:
{
dest->format("<cop{} 0x{:08x}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
}
break;
}
}
}
break;
case InstructionOp::b:
{
const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
const bool link = (rt & u8(0x1E)) == u8(0x10);
if (link)
FormatInstruction(dest, inst, pc, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
else
FormatInstruction(dest, inst, pc, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
}
break;
default:
FormatInstruction(dest, inst, pc, s_base_table[static_cast<u8>(inst.op.GetValue())]);
break;
}
}
void CPU::DisassembleInstructionComment(SmallStringBase* dest, u32 pc, u32 bits)
{
const Instruction inst{bits};
switch (inst.op)
{
case InstructionOp::funct:
FormatComment(dest, inst, pc, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
return;
case InstructionOp::cop0:
case InstructionOp::cop1:
case InstructionOp::cop2:
case InstructionOp::cop3:
{
if (inst.cop.IsCommonInstruction())
{
FormatCopComment(dest, pc, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp());
}
else
{
switch (inst.op)
{
case InstructionOp::cop0:
{
FormatCopComment(dest, pc, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op());
}
break;
case InstructionOp::cop2:
break;
case InstructionOp::cop1:
case InstructionOp::cop3:
default:
{
dest->format("<cop{} 0x{:08x}>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
}
break;
}
}
}
break;
case InstructionOp::b:
{
const u8 rt = static_cast<u8>(inst.i.rt.GetValue());
const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
if (link)
FormatComment(dest, inst, pc, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel");
else
FormatComment(dest, inst, pc, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel");
}
break;
default:
FormatComment(dest, inst, pc, s_base_table[static_cast<u8>(inst.op.GetValue())]);
break;
}
}
const char* CPU::GetGTERegisterName(u32 index)
{
return (index < s_gte_register_names.size()) ? s_gte_register_names[index] : "";
}