[Debugger]

- Assemble dialog box instruction automatically filed with the right disassembly instead of .li and the hex since the hex edition is available in "replace instruction"
- Assemble dialog can now deal with absolute addresses computing automatically the offset. For example assembling b ->0x80359370 at the address 0x8035936c will result in the right relative jump. Add -> before any address you want to use as absolute to respect the disassembly syntax

[Disassembler]
- Adjusted disassembly decorations when using assemble dialog.
      - Right clicking and assembling "rlwinm r0, r0, 2, 0, 29 (3fffffff)" gets "rlwinm r0, r0, 2, 0, 29" in the dialog box.
      - Right clicking and assembling "ps_add p6, p2+p4" gets "ps_add p6, p2, p4" in the dialog box.
- Fixed wrong disassembly of twi / trapi (was missing i before)
- Fixed ps_sel display and variables ordering
- Improved disassembly of ps_ function to support . variants
- Fixed beq+ / beq- disassembly to only support y bit as hint of branch prediction regardless of displacement sign. beq+ -0xC and beq- -0xC are both possible

[Assembler]
1. Added support for pair single p1,...,p31 register for assembly of all ps instructions. "ps_add p6, p2, p4" works instead of "ps_add f6, f2, f4" required before
2. Added support for assembly of Graphics Quantization Registers for assembly. "psq_l p2, 0(r3), 0, qr0" can now be assembled for example

[CICD]
- Added Unit Test for roundtrip between Assembler -> Disassembler -> Assembler
This commit is contained in:
Nitch2024 2025-03-29 11:42:19 -07:00
parent 1981f22228
commit bcefc8ba70
23 changed files with 362 additions and 88 deletions

View File

@ -681,6 +681,7 @@ void GekkoIRPlugin::EvalTerminalRel(Terminal type, const AssemblerToken& tok)
case Terminal::Bin:
case Terminal::GPR:
case Terminal::FPR:
case Terminal::GQR:
case Terminal::SPR:
case Terminal::CRField:
case Terminal::Lt:
@ -732,6 +733,7 @@ void GekkoIRPlugin::EvalTerminalAbs(Terminal type, const AssemblerToken& tok)
case Terminal::Bin:
case Terminal::GPR:
case Terminal::FPR:
case Terminal::GQR:
case Terminal::SPR:
case Terminal::CRField:
case Terminal::Lt:

View File

@ -187,12 +187,14 @@ std::optional<T> EvalIntegral(TokenType tp, std::string_view val)
if (CaseInsensitiveEquals(val, "rtoc"))
return T{2};
[[fallthrough]];
case TokenType::FPR:
case TokenType::FPR: // BE CAREFUL WHAT YOU PUT IN BETWEEN fallthrough and FPR
return std::accumulate(val.begin() + 1, val.end(), T{0}, dec_step);
case TokenType::CRField:
return std::accumulate(val.begin() + 2, val.end(), T{0}, dec_step);
case TokenType::SPR:
return static_cast<T>(*sprg_map.Find(val));
case TokenType::GQR:
return std::accumulate(val.begin() + 2, val.end(), T{0}, dec_step);
case TokenType::Lt:
return T{0};
case TokenType::Gt:
@ -220,6 +222,8 @@ std::string_view TokenTypeToStr(TokenType tp)
return "GPR";
case TokenType::FPR:
return "FPR";
case TokenType::GQR:
return "GQR";
case TokenType::SPR:
return "SPR";
case TokenType::CRField:
@ -668,6 +672,15 @@ TokenType Lexer::ClassifyAlnum() const
{
return TokenType::FPR;
}
else if (std::tolower(alnum[0]) == 'p' && valid_regnum(alnum.substr(1)))
{
return TokenType::FPR;
}
else if (alnum.length() == 3 && CaseInsensitiveEquals(alnum.substr(0, 2), "qr") &&
alnum[2] >= '0' && alnum[2] <= '7')
{
return TokenType::GQR;
}
else if (alnum.length() == 3 && CaseInsensitiveEquals(alnum.substr(0, 2), "cr") &&
alnum[2] >= '0' && alnum[2] <= '7')
{

View File

@ -31,6 +31,7 @@ enum class TokenType
FloatLit,
GPR,
FPR,
GQR,
CRField,
SPR,
Lt,

View File

@ -127,6 +127,9 @@ void ParsePpcBuiltin(ParseState* state)
case TokenType::FPR:
state->plugin.OnTerminal(Terminal::FPR, tok);
break;
case TokenType::GQR:
state->plugin.OnTerminal(Terminal::GQR, tok);
break;
case TokenType::SPR:
state->plugin.OnTerminal(Terminal::SPR, tok);
break;
@ -176,6 +179,7 @@ void ParseBaseexpr(ParseState* state)
case TokenType::GPR:
case TokenType::FPR:
case TokenType::SPR:
case TokenType::GQR:
case TokenType::CRField:
case TokenType::Lt:
case TokenType::Gt:

View File

@ -56,6 +56,7 @@ enum class Terminal
Id,
GPR,
FPR,
GQR,
SPR,
CRField,
Lt,

View File

@ -449,7 +449,7 @@ void GekkoDisassembler::trapi(u32 in, unsigned char dmode)
if (cnd != nullptr)
{
m_opcode = fmt::format("t{}{}", dmode ? 'd' : 'w', cnd);
m_opcode = fmt::format("t{}{}i", dmode ? 'd' : 'w', cnd);
}
else
{
@ -512,8 +512,8 @@ size_t GekkoDisassembler::branch(u32 in, std::string_view bname, int aform, int
char y = (char)(bo & 1);
const char* ext = b_ext[aform * 2 + (int)(in & 1)];
if (bdisp < 0)
y ^= 1;
/* if (bdisp < 0)
y ^= 1; */
y = (y != 0) ? '+' : '-';
if (bo & 4)
@ -631,7 +631,7 @@ void GekkoDisassembler::nooper(u32 in, std::string_view name)
}
}
void GekkoDisassembler::rlw(u32 in, std::string_view name, int i)
void GekkoDisassembler::rlw(u32 in, std::string_view name, int i, bool for_assemble)
{
int s = (int)PPCGETD(in);
int a = (int)PPCGETA(in);
@ -640,8 +640,16 @@ void GekkoDisassembler::rlw(u32 in, std::string_view name, int i)
int me = (int)PPCGETM(in);
m_opcode = fmt::format("rlw{}{}", name, (in & 1) ? "." : "");
m_operands = fmt::format("{}, {}, {}{}, {}, {} ({:08x})", regnames[a], regnames[s], regsel[i],
bsh, mb, me, HelperRotateMask(bsh, mb, me));
if (!for_assemble)
{
m_operands = fmt::format("{}, {}, {}{}, {}, {} ({:08x})", regnames[a], regnames[s], regsel[i],
bsh, mb, me, HelperRotateMask(bsh, mb, me));
}
else
{
m_operands =
fmt::format("{}, {}, {}{}, {}, {}", regnames[a], regnames[s], regsel[i], bsh, mb, me);
}
}
void GekkoDisassembler::ori(u32 in, std::string_view name)
@ -1019,7 +1027,7 @@ void GekkoDisassembler::mtfsb(u32 in, int n)
#define IX ((inst >> 7) & 0x7)
#define WX ((inst >> 10) & 0x1)
void GekkoDisassembler::ps(u32 inst)
void GekkoDisassembler::ps(u32 inst, bool for_assemble)
{
switch ((inst >> 1) & 0x1F)
{
@ -1034,88 +1042,200 @@ void GekkoDisassembler::ps(u32 inst)
return;
case 18:
m_opcode = "ps_div";
m_operands = fmt::format("p{}, p{}/p{}", FD, FA, FB);
m_opcode = fmt::format("ps_div{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}/p{}", FD, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FB);
}
return;
case 20:
m_opcode = "ps_sub";
m_operands = fmt::format("p{}, p{}-p{}", FD, FA, FB);
m_opcode = fmt::format("ps_sub{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}-p{}", FD, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FB);
}
return;
case 21:
m_opcode = "ps_add";
m_operands = fmt::format("p{}, p{}+p{}", FD, FA, FB);
m_opcode = fmt::format("ps_add{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}+p{}", FD, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FB);
}
return;
case 23:
m_opcode = "ps_sel";
m_operands = fmt::format("p{}>=0?p{}:p{}", FD, FA, FC);
m_opcode = fmt::format("ps_sel{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}>=0?p{}:p{}", FD, FA, FC, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 24:
m_opcode = "ps_res";
m_operands = fmt::format("p{}, (1/p{})", FD, FB);
m_opcode = fmt::format("ps_res{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, (1/p{})", FD, FB);
}
else
{
m_operands = fmt::format("p{}, p{}", FD, FB);
}
return;
case 25:
m_opcode = "ps_mul";
m_operands = fmt::format("p{}, p{}*p{}", FD, FA, FC);
m_opcode = fmt::format("ps_mul{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}*p{}", FD, FA, FC);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FC);
}
return;
case 26: // rsqrte
m_opcode = "ps_rsqrte";
m_opcode = fmt::format("ps_rsqrte{}", rcsel[inst & 1]);
m_operands = fmt::format("p{}, p{}", FD, FB);
return;
case 28: // msub
m_opcode = "ps_msub";
m_operands = fmt::format("p{}, p{}*p{}-p{}", FD, FA, FC, FB);
m_opcode = fmt::format("ps_msub{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}*p{}-p{}", FD, FA, FC, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 29: // madd
m_opcode = "ps_madd";
m_operands = fmt::format("p{}, p{}*p{}+p{}", FD, FA, FC, FB);
m_opcode = fmt::format("ps_madd{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}*p{}+p{}", FD, FA, FC, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 30: // nmsub
m_opcode = "ps_nmsub";
m_operands = fmt::format("p{}, -(p{}*p{}-p{})", FD, FA, FC, FB);
m_opcode = fmt::format("ps_nmsub{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, -(p{}*p{}-p{})", FD, FA, FC, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 31: // nmadd
m_opcode = "ps_nmadd";
m_operands = fmt::format("p{}, -(p{}*p{}+p{})", FD, FA, FC, FB);
m_opcode = fmt::format("ps_nmadd{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, -(p{}*p{}+p{})", FD, FA, FC, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 10:
m_opcode = "ps_sum0";
m_operands = fmt::format("p{}, 0=p{}+p{}, 1=p{}", FD, FA, FB, FC);
m_opcode = fmt::format("ps_sum0{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, 0=p{}+p{}, 1=p{}", FD, FA, FB, FC);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 11:
m_opcode = "ps_sum1";
m_operands = fmt::format("p{}, 0=p{}, 1=p{}+p{}", FD, FC, FA, FB);
m_opcode = fmt::format("ps_sum1{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, 0=p{}, 1=p{}+p{}", FD, FC, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 12:
m_opcode = "ps_muls0";
m_operands = fmt::format("p{}, p{}*p{}[0]", FD, FA, FC);
m_opcode = fmt::format("ps_muls0{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}*p{}[0]", FD, FA, FC);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FC);
}
return;
case 13:
m_opcode = "ps_muls1";
m_operands = fmt::format("p{}, p{}*p{}[1]", FD, FA, FC);
m_opcode = fmt::format("ps_muls1{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}*p{}[1]", FD, FA, FC);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FC);
}
return;
case 14:
m_opcode = "ps_madds0";
m_operands = fmt::format("p{}, p{}*p{}[0]+p{}", FD, FA, FC, FB);
m_opcode = fmt::format("ps_madds0{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}*p{}[0]+p{}", FD, FA, FC, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
case 15:
m_opcode = "ps_madds1";
m_operands = fmt::format("p{}, p{}*p{}[1]+p{}", FD, FA, FC, FB);
m_opcode = fmt::format("ps_madds1{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}*p{}[1]+p{}", FD, FA, FC, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}, p{}", FD, FA, FC, FB);
}
return;
}
@ -1123,23 +1243,44 @@ void GekkoDisassembler::ps(u32 inst)
{
// 10-bit suckers (?)
case 40: // nmadd
m_opcode = "ps_neg";
m_operands = fmt::format("p{}, -p{}", FD, FB);
m_opcode = fmt::format("ps_neg{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, -p{}", FD, FB);
}
else
{
m_operands = fmt::format("p{}, p{}", FD, FB);
}
return;
case 72: // nmadd
m_opcode = "ps_mr";
m_opcode = fmt::format("ps_mr{}", rcsel[inst & 1]);
m_operands = fmt::format("p{}, p{}", FD, FB);
return;
case 136:
m_opcode = "ps_nabs";
m_operands = fmt::format("p{}, -|p{}|", FD, FB);
m_opcode = fmt::format("ps_nabs{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, -|p{}|", FD, FB);
}
else
{
m_operands = fmt::format("p{}, p{}", FD, FB);
}
return;
case 264:
m_opcode = "ps_abs";
m_operands = fmt::format("p{}, |p{}|", FD, FB);
m_opcode = fmt::format("ps_abs{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, |p{}|", FD, FB);
}
else
{
m_operands = fmt::format("p{}, p{}", FD, FB);
}
return;
case 0:
@ -1156,23 +1297,51 @@ void GekkoDisassembler::ps(u32 inst)
return;
}
case 528:
m_opcode = "ps_merge00";
m_operands = fmt::format("p{}, p{}[0], p{}[0]", FD, FA, FB);
m_opcode = fmt::format("ps_merge00{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}[0], p{}[0]", FD, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FB);
}
return;
case 560:
m_opcode = "ps_merge01";
m_operands = fmt::format("p{}, p{}[0], p{}[1]", FD, FA, FB);
m_opcode = fmt::format("ps_merge01{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}[0], p{}[1]", FD, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FB);
}
return;
case 592:
m_opcode = "ps_merge10";
m_operands = fmt::format("p{}, p{}[1], p{}[0]", FD, FA, FB);
m_opcode = fmt::format("ps_merge10{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}[1], p{}[0]", FD, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FB);
}
return;
case 624:
m_opcode = "ps_merge11";
m_operands = fmt::format("p{}, p{}[1], p{}[1]", FD, FA, FB);
m_opcode = fmt::format("ps_merge11{}", rcsel[inst & 1]);
if (!for_assemble)
{
m_operands = fmt::format("p{}, p{}[1], p{}[1]", FD, FA, FB);
}
else
{
m_operands = fmt::format("p{}, p{}, p{}", FD, FA, FB);
}
return;
case 1014:
@ -1217,7 +1386,7 @@ void GekkoDisassembler::ps_mem(u32 inst)
// Disassemble PPC instruction and return a pointer to the next
// instruction, or nullptr if an error occurred.
u32* GekkoDisassembler::DoDisassembly(bool big_endian)
u32* GekkoDisassembler::DoDisassembly(bool for_assemble, bool big_endian)
{
u32 in = *m_instr;
@ -1240,7 +1409,7 @@ u32* GekkoDisassembler::DoDisassembly(bool big_endian)
break;
case 4:
ps(in);
ps(in, for_assemble);
break;
case 56:
@ -1361,15 +1530,15 @@ u32* GekkoDisassembler::DoDisassembly(bool big_endian)
break;
case 20:
rlw(in, "imi", 0); // rlwimi
rlw(in, "imi", 0, for_assemble); // rlwimi
break;
case 21:
rlw(in, "inm", 0); // rlwinm
rlw(in, "inm", 0, for_assemble); // rlwinm
break;
case 23:
rlw(in, "nm", 1); // rlwnm
rlw(in, "nm", 1, for_assemble); // rlwnm
break;
case 24:
@ -2275,7 +2444,7 @@ u32* GekkoDisassembler::DoDisassembly(bool big_endian)
// simplified interface
std::string GekkoDisassembler::Disassemble(u32 opcode, u32 current_instruction_address,
bool big_endian)
bool for_assemble, bool big_endian)
{
u32 opc = opcode;
u32 addr = current_instruction_address;
@ -2283,7 +2452,7 @@ std::string GekkoDisassembler::Disassemble(u32 opcode, u32 current_instruction_a
m_instr = (u32*)&opc;
m_iaddr = (u32*)&addr;
DoDisassembly(big_endian);
DoDisassembly(for_assemble, big_endian);
return m_opcode.append("\t").append(m_operands);
}

View File

@ -43,7 +43,7 @@ namespace Common
class GekkoDisassembler final
{
public:
static std::string Disassemble(u32 opcode, u32 current_instruction_address,
static std::string Disassemble(u32 opcode, u32 current_instruction_address, bool for_assemble,
bool big_endian = true);
static const char* GetGPRName(u32 index);
static const char* GetFPRName(u32 index);
@ -67,7 +67,7 @@ private:
static void mcrf(u32 in, std::string_view suffix);
static void crop(u32 in, std::string_view n1, std::string_view n2);
static void nooper(u32 in, std::string_view name);
static void rlw(u32 in, std::string_view name, int i);
static void rlw(u32 in, std::string_view name, int i, bool for_assemble);
static void ori(u32 in, std::string_view name);
static void rld(u32 in, std::string_view name, int i);
static void cmp(u32 in);
@ -85,10 +85,10 @@ private:
static void fdab(u32 in, std::string_view name);
static void fcmp(u32 in, char c);
static void mtfsb(u32 in, int n);
static void ps(u32 inst);
static void ps(u32 inst, bool for_assemble);
static void ps_mem(u32 inst);
static u32* DoDisassembly(bool big_endian);
static u32* DoDisassembly(bool for_assemble, bool big_endian);
enum Flags
{

View File

@ -62,7 +62,8 @@ public:
// Threads
virtual Common::Debug::Threads GetThreads(const CPUThreadGuard& guard) const = 0;
virtual std::string Disassemble(const CPUThreadGuard* /*guard*/, u32 /*address*/) const
virtual std::string Disassemble(const CPUThreadGuard* /*guard*/, u32 /*address*/,
bool /*for_assemble*/) const
{
return "NODEBUGGER";
}

View File

@ -276,7 +276,8 @@ Common::Debug::Threads PPCDebugInterface::GetThreads(const Core::CPUThreadGuard&
return threads;
}
std::string PPCDebugInterface::Disassemble(const Core::CPUThreadGuard* guard, u32 address) const
std::string PPCDebugInterface::Disassemble(const Core::CPUThreadGuard* guard, u32 address,
bool for_assemble) const
{
if (guard)
{
@ -286,7 +287,7 @@ std::string PPCDebugInterface::Disassemble(const Core::CPUThreadGuard* guard, u3
}
const u32 op = PowerPC::MMU::HostRead_Instruction(*guard, address);
std::string disasm = Common::GekkoDisassembler::Disassemble(op, address);
std::string disasm = Common::GekkoDisassembler::Disassemble(op, address, for_assemble);
const UGeckoInstruction inst{op};
if (inst.OPCD == 1)

View File

@ -75,7 +75,8 @@ public:
// Threads
Common::Debug::Threads GetThreads(const Core::CPUThreadGuard& guard) const override;
std::string Disassemble(const Core::CPUThreadGuard* guard, u32 address) const override;
std::string Disassemble(const Core::CPUThreadGuard* guard, u32 address,
bool for_assemble = false) const override;
std::string GetRawMemoryString(const Core::CPUThreadGuard& guard, int memory,
u32 address) const override;
bool IsAlive() const override;

View File

@ -454,7 +454,7 @@ void CachedInterpreter::LogGeneratedCode() const
std::span{m_code_buffer.data(), code_block.m_num_instructions})
{
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address, false));
}
stream << "\nHost Code:\n";

View File

@ -97,7 +97,8 @@ void Interpreter::Trace(const UGeckoInstruction& inst)
fregs += fmt::format("f{:02d}: {:08x} {:08x} ", i, ps.PS0AsU64(), ps.PS1AsU64());
}
const std::string ppc_inst = Common::GekkoDisassembler::Disassemble(inst.hex, m_ppc_state.pc);
const std::string ppc_inst =
Common::GekkoDisassembler::Disassemble(inst.hex, m_ppc_state.pc, false);
DEBUG_LOG_FMT(POWERPC,
"INTER PC: {:08x} SRR0: {:08x} SRR1: {:08x} CRval: {:016x} "
"FPSCR: {:08x} MSR: {:08x} LR: {:08x} {} {:08x} {}",
@ -294,7 +295,7 @@ void Interpreter::unknown_instruction(Interpreter& interpreter, UGeckoInstructio
const u32 last_pc = interpreter.m_last_pc;
const u32 opcode = PowerPC::MMU::HostRead_U32(guard, last_pc);
const std::string disasm = Common::GekkoDisassembler::Disassemble(opcode, last_pc);
const std::string disasm = Common::GekkoDisassembler::Disassemble(opcode, last_pc, false);
NOTICE_LOG_FMT(POWERPC, "Last PC = {:08x} : {}", last_pc, disasm);
Dolphin_Debugger::PrintCallstack(guard, Common::Log::LogType::POWERPC,
Common::Log::LogLevel::LNOTICE);

View File

@ -1177,7 +1177,8 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
#if defined(_DEBUG) || defined(DEBUGFAST)
if (!gpr.SanityCheck() || !fpr.SanityCheck())
{
const std::string ppc_inst = Common::GekkoDisassembler::Disassemble(op.inst.hex, em_address);
const std::string ppc_inst =
Common::GekkoDisassembler::Disassemble(op.inst.hex, em_address, false);
NOTICE_LOG_FMT(DYNA_REC, "Unflushed register: {}", ppc_inst);
}
#endif
@ -1320,7 +1321,7 @@ void Jit64::LogGeneratedCode() const
std::span{m_code_buffer.data(), code_block.m_num_instructions})
{
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address, false));
}
const JitBlock* const block = js.curBlock;

View File

@ -1401,7 +1401,7 @@ void JitArm64::LogGeneratedCode() const
std::span{m_code_buffer.data(), code_block.m_num_instructions})
{
fmt::print(stream, "0x{:08x}\t\t{}\n", op.address,
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address));
Common::GekkoDisassembler::Disassemble(op.inst.hex, op.address, false));
}
const JitBlock* const block = js.curBlock;

View File

@ -38,8 +38,9 @@ QString HtmlFormatErrorLine(const Common::GekkoAssembler::AssemblerError& err)
}
} // namespace
AssembleInstructionDialog::AssembleInstructionDialog(QWidget* parent, u32 address, u32 value)
: QDialog(parent), m_code(value), m_address(address)
AssembleInstructionDialog::AssembleInstructionDialog(QWidget* parent, u32 address, u32 value,
QString disasm)
: QDialog(parent), m_code(value), m_address(address), m_disassembly(disasm)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setWindowModality(Qt::WindowModal);
@ -66,7 +67,7 @@ void AssembleInstructionDialog::CreateWidgets()
layout->addWidget(m_error_line_label);
layout->addWidget(m_msg_label);
layout->addWidget(m_button_box);
m_input_edit->setText(QStringLiteral(".4byte 0x%1").arg(m_code, 8, 16, QLatin1Char('0')));
m_input_edit->setText(m_disassembly);
setLayout(layout);
OnEditChanged();
@ -86,6 +87,31 @@ void AssembleInstructionDialog::OnEditChanged()
std::string line = m_input_edit->text().toStdString();
Common::ToLower(&line);
size_t posArrow = line.find("->");
if (posArrow != std::string::npos)
{
std::string start = line.substr(0, posArrow);
std::string dest = line.substr(posArrow + 2);
u32 destAddress = 0;
size_t posHex = dest.find("0x");
if (posHex == std::string::npos)
{
destAddress = (u32)strtoul(dest.c_str(), nullptr, 10);
}
else
{
destAddress = (u32)strtoul(dest.substr(posHex + 2).c_str(), nullptr, 16);
}
if (destAddress < m_address)
{
line = start + " -" + std::to_string(m_address - destAddress);
}
else
{
line = start + " " + std::to_string(destAddress - m_address);
}
}
FailureOr<std::vector<CodeBlock>> asm_result = Assemble(line, m_address);
if (IsFailure(asm_result))

View File

@ -15,7 +15,7 @@ class AssembleInstructionDialog : public QDialog
{
Q_OBJECT
public:
explicit AssembleInstructionDialog(QWidget* parent, u32 address, u32 value);
explicit AssembleInstructionDialog(QWidget* parent, u32 address, u32 value, QString disasm);
u32 GetCode() const;
@ -28,6 +28,7 @@ private:
u32 m_code;
u32 m_address;
QString m_disassembly;
QLineEdit* m_input_edit;
QLabel* m_error_loc_label;
QLabel* m_error_line_label;

View File

@ -306,7 +306,7 @@ static QVariant GetValidSymbolStringVariant(const QVariant& symbol_name_v)
static QString GetInstructionMnemonic(u32 hex)
{
const std::string disas = Common::GekkoDisassembler::Disassemble(hex, 0);
const std::string disas = Common::GekkoDisassembler::Disassemble(hex, 0, false);
const std::string::size_type split = disas.find('\t');
// I wish I could disassemble just the mnemonic!
if (split == std::string::npos)

View File

@ -228,7 +228,7 @@ void CodeViewWidget::FontBasedSizing()
// Similarly, the longest parameter set is 'rtoc, rtoc, r10, 10, 10 (00000800)' (0x5c425294u),
// but one is unlikely to encounter that in practice, so let's use a slightly more reasonable
// 'r31, r31, 16, 16, 31 (ffff0000)'. The user can resize the columns as necessary anyway.
const std::string disas = Common::GekkoDisassembler::Disassemble(0x57ff843fu, 0);
const std::string disas = Common::GekkoDisassembler::Disassemble(0x57ff843fu, 0, false);
const auto split = disas.find('\t');
const std::string ins = (split == std::string::npos ? disas : disas.substr(0, split));
const std::string param = (split == std::string::npos ? "" : disas.substr(split + 1));
@ -1030,7 +1030,12 @@ void CodeViewWidget::DoPatchInstruction(bool assemble)
if (assemble)
{
AssembleInstructionDialog dialog(this, addr, debug_interface.ReadInstruction(guard, addr));
std::string code_line =
m_system.GetPowerPC().GetDebugInterface().Disassemble(&guard, addr, true);
std::ranges::replace(code_line, '\t', ' ');
AssembleInstructionDialog dialog(this, addr, debug_interface.ReadInstruction(guard, addr),
QString::fromStdString(code_line));
SetQWidgetWindowDecorations(&dialog);
if (dialog.exec() == QDialog::Accepted)
{

View File

@ -55,6 +55,10 @@ public:
HighlightCurToken(HighlightFormat::FPR);
break;
case Terminal::GQR:
HighlightCurToken(HighlightFormat::GQR);
break;
case Terminal::SPR:
HighlightCurToken(HighlightFormat::SPR);
break;
@ -221,6 +225,7 @@ void GekkoSyntaxHighlight::HighlightSubstr(int start, int len, HighlightFormat f
break;
case HighlightFormat::GPR:
case HighlightFormat::FPR:
case HighlightFormat::GQR:
case HighlightFormat::SPR:
case HighlightFormat::CRField:
case HighlightFormat::CRFlag:

View File

@ -18,6 +18,7 @@ enum class HighlightFormat
Immediate,
GPR,
FPR,
GQR,
SPR,
CRField,
CRFlag,

View File

@ -213,7 +213,7 @@ static void DisassembleCodeBuffer(const JitBlock& block, PPCSymbolDB& ppc_symbol
next_address = address;
}
fmt::print(stream, "0x{:08x}\t{}\n", address,
Common::GekkoDisassembler::Disassemble(inst.hex, address));
Common::GekkoDisassembler::Disassemble(inst.hex, address, false));
next_address += sizeof(UGeckoInstruction);
}
}

View File

@ -55,9 +55,9 @@ void PatchInstructionDialog::OnEditChanged()
m_button_box->button(QDialogButtonBox::Ok)->setEnabled(good);
m_preview_label->setText(
tr("Instruction: %1")
.arg(QString::fromStdString(Common::GekkoDisassembler::Disassemble(m_code, m_address))));
m_preview_label->setText(tr("Instruction: %1")
.arg(QString::fromStdString(Common::GekkoDisassembler::Disassemble(
m_code, m_address, false))));
}
u32 PatchInstructionDialog::GetCode() const

View File

@ -6,7 +6,9 @@
#include <string_view>
#include <vector>
#include <fmt/format.h>
#include "Common/Assembler/GekkoAssembler.h"
#include "Common/GekkoDisassembler.h"
using namespace Common::GekkoAssembler;
@ -456,6 +458,45 @@ TEST(Assembler, AllInstructions)
}
}
TEST(Assembler, RoundTripTest)
{
auto res = Assemble(instructions, 0);
ASSERT_TRUE(!IsFailure(res));
auto&& code_blocks = GetT(res);
ASSERT_EQ(code_blocks.size(), 1);
ASSERT_EQ(code_blocks[0].instructions.size(), sizeof(expected_instructions));
ASSERT_EQ(code_blocks[0].instructions.size() & 3, 0);
for (size_t i = 0; i < code_blocks[0].instructions.size(); i += sizeof(u32))
{
const u32 hex_code =
(code_blocks[0].instructions[i] << 24) | (code_blocks[0].instructions[i + 1] << 16) |
(code_blocks[0].instructions[i + 2] << 8) | code_blocks[0].instructions[i + 3];
std::string text_code = Common::GekkoDisassembler::Disassemble(hex_code, 0, true);
// NEED TO FIX ABSOLUTE ADDRESSES COMING IN FROM DISASSEMBLER
size_t posArrow = text_code.find("->");
if (posArrow != std::string::npos)
{
u32 address = (u32)strtoul(text_code.substr(posArrow + 2).c_str(), nullptr, 16) & 0xFFFF;
if (address & 0x8000)
{
text_code = fmt::format("{} -0x{:04X}", text_code.substr(0, posArrow), 0x10000 - address);
}
else
{
text_code = fmt::format("{} 0x{:04X}", text_code.substr(0, posArrow), address);
}
}
auto recompiled = Assemble(text_code, 0);
auto&& code_blocks_reassembled = GetT(recompiled);
for (size_t j = 0; j < sizeof(u32); j++)
{
EXPECT_EQ(code_blocks[0].instructions[i + j], code_blocks_reassembled[0].instructions[j])
<< " -> i=" << i;
}
}
}
constexpr char extended_instructions[] = "subi 0, 4, 8\n"
"subis 0, 4, 8\n"
"subic 0, 4, 8\n"