Files
psxrecomp/recompiler/src/mips_decoder.cpp
T
Matthew Stanley 8e81afd111 fix: conditional branch fall-through + Tier 1 reverse debugger
Recompiler bug fix: full_function_emitter treated conditional branch
delay slots at function boundaries as fully resolved control flow,
suppressing the fall-through tail call for the "not taken" path.
This broke 15 cross-function fall-throughs across the BIOS, most
critically the find-free-EvCB-slot function (BFC11800) whose search
loop was silently skipped — causing OpenEvent to return garbage handles
(0xF100E1E8) instead of valid slot indices (0xF1000000..0xF1000005).

Fix: only suppress fall-through for unconditional flow (j/jr/jal/jalr/rfe).
Conditional branches (beq/bne/blez/bgtz/bltz/bgez) now always emit
`cpu->pc = next_addr; return;` for the not-taken path.

Also included:
- Tier 1 reverse debugger: 1M-entry write trace ring (32 MB),
  64K-entry MMIO trace ring (2 MB), 8-slot multi-range address
  filtering, function attribution via g_debug_current_func_addr.
  6 new TCP commands: wtrace_add/del/ranges/stats, mmio_dump/clear.
- 3 new dispatch seeds for functions exposed by fall-through fix
  (SIO epilogue, exception chain walker, GTE init).
- Diagnostic tools: analyze_sio_trace, decode_insn, decode_jt, mmio_tally.
- Pattern 5 trampoline fix + PLAN.md Phase 4 status update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-21 14:23:09 -07:00

336 lines
13 KiB
C++

#include "mips_decoder.h"
namespace PSXRecomp {
// Register names (MIPS O32 ABI)
static const char* kRegisterNames[32] = {
"$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3",
"$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7",
"$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
"$t8", "$t9", "$k0", "$k1", "$gp", "$sp", "$fp", "$ra"
};
const char* MipsDecoder::register_name(uint8_t reg) {
if (reg >= 32) return "$??";
return kRegisterNames[reg];
}
bool MipsDecoder::is_nop(uint32_t instr) {
return instr == 0;
}
uint32_t MipsDecoder::compute_branch_target(uint32_t address, int16_t imm16) {
// Branch target = PC + 4 + (sign_extended_offset << 2)
return address + 4u + (uint32_t)((int32_t)imm16 << 2);
}
uint32_t MipsDecoder::compute_jump_target(uint32_t address, uint32_t target26) {
// Jump target = ((PC+4) & 0xF0000000) | (target26 << 2)
return ((address + 4u) & 0xF0000000u) | (target26 << 2u);
}
const char* MipsDecoder::get_mnemonic(const DecodedInstruction& instr) {
return instr.mnemonic ? instr.mnemonic : "???";
}
// ---------------------------------------------------------------------------
// SPECIAL opcode (opcode == 0): funct field [5:0] determines operation
// ---------------------------------------------------------------------------
void MipsDecoder::decode_special(DecodedInstruction& out) {
out.format = InstrFormat::SPECIAL;
switch (out.funct) {
case 0x00: out.mnemonic = "SLL"; break;
case 0x02: out.mnemonic = "SRL"; break;
case 0x03: out.mnemonic = "SRA"; break;
case 0x04: out.mnemonic = "SLLV"; break;
case 0x06: out.mnemonic = "SRLV"; break;
case 0x07: out.mnemonic = "SRAV"; break;
case 0x08: out.mnemonic = "JR"; break;
case 0x09: out.mnemonic = "JALR"; break;
case 0x0C: out.mnemonic = "SYSCALL"; break;
case 0x0D: out.mnemonic = "BREAK"; break;
case 0x10: out.mnemonic = "MFHI"; break;
case 0x11: out.mnemonic = "MTHI"; break;
case 0x12: out.mnemonic = "MFLO"; break;
case 0x13: out.mnemonic = "MTLO"; break;
case 0x18: out.mnemonic = "MULT"; break;
case 0x19: out.mnemonic = "MULTU"; break;
case 0x1A: out.mnemonic = "DIV"; break;
case 0x1B: out.mnemonic = "DIVU"; break;
case 0x20: out.mnemonic = "ADD"; break;
case 0x21: out.mnemonic = "ADDU"; break;
case 0x22: out.mnemonic = "SUB"; break;
case 0x23: out.mnemonic = "SUBU"; break;
case 0x24: out.mnemonic = "AND"; break;
case 0x25: out.mnemonic = "OR"; break;
case 0x26: out.mnemonic = "XOR"; break;
case 0x27: out.mnemonic = "NOR"; break;
case 0x2A: out.mnemonic = "SLT"; break;
case 0x2B: out.mnemonic = "SLTU"; break;
default:
out.mnemonic = "SPECIAL?";
out.format = InstrFormat::UNKNOWN;
break;
}
}
// ---------------------------------------------------------------------------
// REGIMM opcode (opcode == 1): rt field [20:16] determines operation
// ---------------------------------------------------------------------------
void MipsDecoder::decode_regimm(DecodedInstruction& out) {
out.format = InstrFormat::REGIMM;
switch (out.rt) {
case 0x00: out.mnemonic = "BLTZ"; break;
case 0x01: out.mnemonic = "BGEZ"; break;
case 0x10: out.mnemonic = "BLTZAL"; break;
case 0x11: out.mnemonic = "BGEZAL"; break;
default:
out.mnemonic = "REGIMM?";
out.format = InstrFormat::UNKNOWN;
break;
}
}
// ---------------------------------------------------------------------------
// COP0 (opcode == 0x10): rs field [25:21] determines sub-operation
// ---------------------------------------------------------------------------
void MipsDecoder::decode_cop0(DecodedInstruction& out) {
out.format = InstrFormat::COP0;
// rs field holds the COP sub-opcode
switch (out.rs) {
case 0x00: out.mnemonic = "MFC0"; break;
case 0x04: out.mnemonic = "MTC0"; break;
case 0x10:
// CO bit set — could be RFE (funct==0x10) or TLBR/TLBWI/etc.
if (out.funct == 0x10) out.mnemonic = "RFE";
else out.mnemonic = "COP0CO";
break;
default: out.mnemonic = "COP0"; break;
}
}
// ---------------------------------------------------------------------------
// COP2 / GTE (opcode == 0x12): rs field determines sub-operation
// ---------------------------------------------------------------------------
void MipsDecoder::decode_cop2(DecodedInstruction& out) {
out.format = InstrFormat::COP2;
switch (out.rs) {
case 0x00: out.mnemonic = "MFC2"; break;
case 0x02: out.mnemonic = "CFC2"; break;
case 0x04: out.mnemonic = "MTC2"; break;
case 0x06: out.mnemonic = "CTC2"; break;
default:
// CO bit (bit 25) set => GTE command word
if (out.rs & 0x10) out.mnemonic = "GTE";
else out.mnemonic = "COP2";
break;
}
}
// ---------------------------------------------------------------------------
// Classification flags (called after mnemonic/format are set)
// ---------------------------------------------------------------------------
void MipsDecoder::set_classification_flags(DecodedInstruction& out) {
out.is_branch = false;
out.is_jump = false;
out.is_load = false;
out.is_store = false;
out.is_alu = false;
out.is_nop = (out.raw == 0);
out.is_syscall = false;
out.is_break = false;
out.is_jr_ra = false;
out.is_delay_slot_user = false;
if (out.format == InstrFormat::UNKNOWN) return;
if (out.is_nop) return;
// SPECIAL (opcode == 0)
if (out.opcode == 0x00) {
switch (out.funct) {
case 0x08: // JR
out.is_jump = true;
out.is_delay_slot_user = true;
if (out.rs == 31) out.is_jr_ra = true;
break;
case 0x09: // JALR
out.is_jump = true;
out.is_delay_slot_user = true;
break;
case 0x0C: out.is_syscall = true; break;
case 0x0D: out.is_break = true; break;
default: out.is_alu = true; break;
}
return;
}
// REGIMM (opcode == 1) — all are conditional branches
if (out.opcode == 0x01) {
out.is_branch = true;
out.is_delay_slot_user = true;
return;
}
switch (out.opcode) {
// Unconditional jumps
case 0x02: // J
case 0x03: // JAL
out.is_jump = true;
out.is_delay_slot_user = true;
break;
// Conditional branches
case 0x04: // BEQ
case 0x05: // BNE
case 0x06: // BLEZ
case 0x07: // BGTZ
out.is_branch = true;
out.is_delay_slot_user = true;
break;
// ALU (immediate variants)
case 0x08: // ADDI
case 0x09: // ADDIU
case 0x0A: // SLTI
case 0x0B: // SLTIU
case 0x0C: // ANDI
case 0x0D: // ORI
case 0x0E: // XORI
case 0x0F: // LUI
out.is_alu = true;
break;
// Loads
case 0x20: // LB
case 0x21: // LH
case 0x22: // LWL
case 0x23: // LW
case 0x24: // LBU
case 0x25: // LHU
case 0x26: // LWR
case 0x32: // LWC2
out.is_load = true;
break;
// Stores
case 0x28: // SB
case 0x29: // SH
case 0x2A: // SWL
case 0x2B: // SW
case 0x2E: // SWR
case 0x3A: // SWC2
out.is_store = true;
break;
// COP0, COP2: neither load/store/alu/branch/jump
default:
break;
}
}
// ---------------------------------------------------------------------------
// Main decode function
// ---------------------------------------------------------------------------
DecodedInstruction MipsDecoder::decode(uint32_t instr, uint32_t address) {
DecodedInstruction out{};
out.raw = instr;
out.address = address;
out.mnemonic = "???";
out.format = InstrFormat::UNKNOWN;
// Extract all bit fields
out.opcode = (instr >> 26) & 0x3F;
out.rs = (instr >> 21) & 0x1F;
out.rt = (instr >> 16) & 0x1F;
out.rd = (instr >> 11) & 0x1F;
out.shamt = (instr >> 6) & 0x1F;
out.funct = (instr ) & 0x3F;
out.uimm16 = (uint16_t)(instr & 0xFFFF);
out.imm16 = (int16_t)(instr & 0xFFFF);
out.target26 = instr & 0x03FFFFFF;
out.sign_ext_imm = (int32_t)out.imm16;
// Dispatch to format-specific decoder
switch (out.opcode) {
case 0x00: decode_special(out); break;
case 0x01: decode_regimm(out); break;
case 0x02: out.mnemonic = "J"; out.format = InstrFormat::J; break;
case 0x03: out.mnemonic = "JAL"; out.format = InstrFormat::J; break;
case 0x04: out.mnemonic = "BEQ"; out.format = InstrFormat::I; break;
case 0x05: out.mnemonic = "BNE"; out.format = InstrFormat::I; break;
case 0x06: out.mnemonic = "BLEZ"; out.format = InstrFormat::I; break;
case 0x07: out.mnemonic = "BGTZ"; out.format = InstrFormat::I; break;
case 0x08: out.mnemonic = "ADDI"; out.format = InstrFormat::I; break;
case 0x09: out.mnemonic = "ADDIU"; out.format = InstrFormat::I; break;
case 0x0A: out.mnemonic = "SLTI"; out.format = InstrFormat::I; break;
case 0x0B: out.mnemonic = "SLTIU"; out.format = InstrFormat::I; break;
case 0x0C: out.mnemonic = "ANDI"; out.format = InstrFormat::I; break;
case 0x0D: out.mnemonic = "ORI"; out.format = InstrFormat::I; break;
case 0x0E: out.mnemonic = "XORI"; out.format = InstrFormat::I; break;
case 0x0F: out.mnemonic = "LUI"; out.format = InstrFormat::I; break;
case 0x10: decode_cop0(out); break;
case 0x12: decode_cop2(out); break;
case 0x20: out.mnemonic = "LB"; out.format = InstrFormat::I; break;
case 0x21: out.mnemonic = "LH"; out.format = InstrFormat::I; break;
case 0x22: out.mnemonic = "LWL"; out.format = InstrFormat::I; break;
case 0x23: out.mnemonic = "LW"; out.format = InstrFormat::I; break;
case 0x24: out.mnemonic = "LBU"; out.format = InstrFormat::I; break;
case 0x25: out.mnemonic = "LHU"; out.format = InstrFormat::I; break;
case 0x26: out.mnemonic = "LWR"; out.format = InstrFormat::I; break;
case 0x28: out.mnemonic = "SB"; out.format = InstrFormat::I; break;
case 0x29: out.mnemonic = "SH"; out.format = InstrFormat::I; break;
case 0x2A: out.mnemonic = "SWL"; out.format = InstrFormat::I; break;
case 0x2B: out.mnemonic = "SW"; out.format = InstrFormat::I; break;
case 0x2E: out.mnemonic = "SWR"; out.format = InstrFormat::I; break;
case 0x32: out.mnemonic = "LWC2"; out.format = InstrFormat::I; break;
case 0x3A: out.mnemonic = "SWC2"; out.format = InstrFormat::I; break;
default:
out.mnemonic = "???";
out.format = InstrFormat::UNKNOWN;
break;
}
// Compute branch/jump targets
if (out.opcode == 0x02 || out.opcode == 0x03) {
// J / JAL
out.jump_target = compute_jump_target(address, out.target26);
}
if (out.opcode == 0x04 || out.opcode == 0x05 ||
out.opcode == 0x06 || out.opcode == 0x07 ||
out.opcode == 0x01) {
// BEQ/BNE/BLEZ/BGTZ + REGIMM (BLTZ/BGEZ/etc.)
out.branch_target = compute_branch_target(address, out.imm16);
}
// Set classification flags
set_classification_flags(out);
// Override mnemonic for NOP (0x00000000 = SLL $zero,$zero,0)
if (out.raw == 0) {
out.mnemonic = "NOP";
}
return out;
}
// ---------------------------------------------------------------------------
// Decode all instructions in a PS1 executable
// ---------------------------------------------------------------------------
std::vector<DecodedInstruction> MipsDecoder::decode_executable(
const PS1Executable& exe)
{
std::vector<DecodedInstruction> result;
const uint32_t word_count = exe.code_size() / 4;
result.reserve(word_count);
for (uint32_t addr = exe.load_address(); addr < exe.end_address(); addr += 4) {
auto word = exe.read_word(addr);
if (!word.has_value()) break;
result.push_back(decode(*word, addr));
}
return result;
}
} // namespace PSXRecomp