diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index ecf0253c..7323acb1 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -77,6 +77,26 @@ int g_precise_mode = 0; int g_ls_dirty_observe = 0; extern int g_ls_replay_active; /* defined in the lockstep section; used by exec_one's jal/jalr guard */ +/* ===== Minimal exception delivery from the interpreter path ===== + * Sets COP0 registers (BadVAddr, Cause, EPC, Status) and redirects PC to the + * hardware exception vector. Used for alignment errors caught by the + * interpreter. Returns 1 (control transferred). */ +static int interp_exception(CPUState *cpu, uint32_t exc_code, + uint32_t badvaddr, uint32_t epc_pc) { + uint32_t sr = cpu->cop0[12]; + /* BadVAddr */ + cpu->cop0[8] = badvaddr; + /* Cause: ExcCode, clear BD (not tracking delay-slot exception here) */ + cpu->cop0[13] = (cpu->cop0[13] & ~0x8000007Cu) | (exc_code << 2); + /* Push SR exception stack: shift bits [5:0] left by 2 */ + cpu->cop0[12] = (sr & ~0x3Fu) | ((sr & 0x0Fu) << 2); + /* EPC */ + cpu->cop0[14] = epc_pc; + /* Vector: BEV selects between KSEG1 (BIOS ROM) and KSEG0 (RAM) */ + cpu->pc = (sr & 0x00400000u) ? 0xBFC00180u : 0x80000080u; + return 1; +} + #ifdef PSX_COSIM static int g_cosim_exec_one_hooked = 0; static void cosim_exec_one_begin(void) { g_cosim_exec_one_hooked = 0; } @@ -1248,6 +1268,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { return 0; case 0x08: { /* JR rs */ uint32_t target = cpu->gpr[rs]; + if (target & 3) return interp_exception(cpu, 4, target, pc); /* LoadAddressError */ exec_delay_slot(cpu, pc + 4); cosim_exec_one_transfer_hook(pc + 4); /* crossing (if target is compiled) is counted at the block-loop @@ -1258,6 +1279,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { } case 0x09: { /* JALR rd, rs */ uint32_t target = cpu->gpr[rs]; + if (target & 3) return interp_exception(cpu, 4, target, pc); /* LoadAddressError */ uint32_t return_pc = pc + 8; cpu->gpr[rd ? rd : 31] = return_pc; cpu->gpr[0] = 0; @@ -1587,18 +1609,74 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { #ifdef PSX_ENABLE_BLOCK_CYCLES cpu->ld_absorb = 0u; cpu->ld_which_t = (uint8_t)rt; +#endif + cpu->gpr[rt] = cpu->cop0[rd]; + cpu->gpr[0] = 0; + return 0; + } + if (cop_op == 0x02) { /* CFC0 — identical to MFC0 on PSX */ +#ifdef PSX_ENABLE_BLOCK_CYCLES + cpu->ld_absorb = 0u; + cpu->ld_which_t = (uint8_t)rt; #endif cpu->gpr[rt] = cpu->cop0[rd]; cpu->gpr[0] = 0; return 0; } if (cop_op == 0x04) { /* MTC0 */ - cpu->cop0[rd] = cpu->gpr[rt]; + uint32_t val = cpu->gpr[rt]; + if (rd == 13) { + /* Cause register: bits [15:10] are software IP (R/W), + * bits [9:8] are hardware IP (read-only from software), + * ExcCode [6:2] is read-only. Only allow writes to [15:10]. */ + cpu->cop0[13] = (cpu->cop0[13] & 0x03FFu) | (val & 0xFC00u); + } else { + cpu->cop0[rd] = val; + } + /* psxTestSWInts: after writing Status or Cause, check if a + * software interrupt is now deliverable (Cause & Status & 0x0300 + * with Status.IEc set). Matches PCSX-Redux's MTC0 path. */ + if ((rd == 12 /* Status */ || rd == 13 /* Cause */) && + (cpu->cop0[13] & cpu->cop0[12] & 0x0300u) && + (cpu->cop0[12] & 0x1u)) { + g_dirty_safe_resume_pc = pc + 4; + cpu->pc = pc + 4; + psx_check_interrupts(cpu); + g_dirty_safe_resume_pc = 0; + return (cpu->pc != pc + 4); /* transferred if exception taken */ + } + return 0; + } + if (cop_op == 0x06) { /* CTC0 — identical to MTC0 on PSX */ + uint32_t val = cpu->gpr[rt]; + if (rd == 13) { + cpu->cop0[13] = (cpu->cop0[13] & 0x03FFu) | (val & 0xFC00u); + } else { + cpu->cop0[rd] = val; + } + if ((rd == 12 || rd == 13) && + (cpu->cop0[13] & cpu->cop0[12] & 0x0300u) && + (cpu->cop0[12] & 0x1u)) { + g_dirty_safe_resume_pc = pc + 4; + cpu->pc = pc + 4; + psx_check_interrupts(cpu); + g_dirty_safe_resume_pc = 0; + return (cpu->pc != pc + 4); + } return 0; } if (cop_op == 0x10 && fnt == 0x10) { /* RFE */ uint32_t sr = cpu->cop0[12]; cpu->cop0[12] = (sr & 0xFFFFFFF0u) | ((sr >> 2) & 0x0Fu); + /* psxTestSWInts: after RFE, interrupts may become deliverable. */ + if ((cpu->cop0[13] & cpu->cop0[12] & 0x0300u) && + (cpu->cop0[12] & 0x1u)) { + g_dirty_safe_resume_pc = pc + 4; + cpu->pc = pc + 4; + psx_check_interrupts(cpu); + g_dirty_safe_resume_pc = 0; + return (cpu->pc != pc + 4); + } return 0; } return abort_unsupported(pc, insn, "COP0 op"); @@ -1656,6 +1734,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { } case 0x21: { /* LH */ uint32_t addr = cpu->gpr[rs] + (uint32_t)simm; + if (addr & 1) return interp_exception(cpu, 4, addr, pc); /* LoadAddressError */ cpu->gpr[rt] = (uint32_t)(int32_t)(int16_t)psx_cyc_load_half(cpu, addr, rt, 1u << rs); cpu->gpr[0] = 0; return 0; @@ -1669,6 +1748,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { } case 0x23: { /* LW */ uint32_t addr = cpu->gpr[rs] + (uint32_t)simm; + if (addr & 3) return interp_exception(cpu, 4, addr, pc); /* LoadAddressError */ cpu->gpr[rt] = psx_cyc_load_word(cpu, addr, rt, 1u << rs); cpu->gpr[0] = 0; return 0; @@ -1681,6 +1761,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { } case 0x25: { /* LHU */ uint32_t addr = cpu->gpr[rs] + (uint32_t)simm; + if (addr & 1) return interp_exception(cpu, 4, addr, pc); /* LoadAddressError */ cpu->gpr[rt] = (uint32_t)psx_cyc_load_half(cpu, addr, rt, 1u << rs); cpu->gpr[0] = 0; return 0; @@ -1699,6 +1780,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { } case 0x29: { /* SH */ uint32_t addr = cpu->gpr[rs] + (uint32_t)simm; + if (addr & 1) return interp_exception(cpu, 5, addr, pc); /* StoreAddressError */ uint16_t val = (uint16_t)cpu->gpr[rt]; /* Widescreen backdrop screenX squash on the interpreter path: mirrors * the recompiler emit at [widescreen.backdrop] x_sites. Overlay code @@ -1717,6 +1799,7 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { } case 0x2B: { /* SW */ uint32_t addr = cpu->gpr[rs] + (uint32_t)simm; + if (addr & 3) return interp_exception(cpu, 5, addr, pc); /* StoreAddressError */ cpu->write_word(addr, cpu->gpr[rt]); return 0; }