fix(cpu): PPCBUG-063/064/065 trap PC + sc LEV + twi typed-trap logging

Phase 6 batch 1 — trap/sc semantics.

- PPCBUG-063 trap PC: previously ctx.pc was incremented to CIA+4 BEFORE
  StepResult::Trap returned, forcing handlers to .wrapping_sub(4) to
  recover the faulting instruction address. Now ctx.pc stays at CIA on
  trap, matching SRR0 semantics on real hardware. Critical for any
  future SEH/exception-delivery path (e.g. the Sylpheed C++ throw work).
- PPCBUG-065 typed-trap logging: `twi 31, r0, IMM` is the Xbox 360
  CRT/kernel typed-trap convention encoding C++ exception class via
  SIMM. The trace now logs the SIMM type code when this pattern fires.
  Routing the type code via a StepResult payload requires an enum
  extension (multiple consumer sites) that's deferred.
- PPCBUG-064 sc LEV logging: `sc 2` is the Xbox 360 hypervisor-call
  convention; canary dispatches it to a different handler than `sc 0`.
  Now logs a warning when LEV != 0. Routing LEV=2 to a HypervisorCall
  variant also requires a StepResult enum extension; deferred.

The two enum-extension follow-ups can land as a structural sub-batch
once a clear consumer (SEH dispatch, hypervisor-call HLE) is in place.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-02 13:42:50 +02:00
parent 9f88e275b8
commit d96986a10e

View File

@@ -982,6 +982,16 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
// ===== System call ===== // ===== System call =====
PpcOpcode::sc => { PpcOpcode::sc => {
// PPCBUG-064: log non-zero LEV (`sc 2` is the Xbox 360 hypervisor-call
// convention; canary dispatches it to a different handler than `sc 0`).
// Routing LEV=2 requires a StepResult variant extension; deferred.
let lev = (instr.raw >> 5) & 0x7F;
if lev != 0 {
tracing::warn!(
"sc with LEV={} at {:#010x}: dispatched as plain SystemCall (HVcall routing not implemented)",
lev, ctx.pc
);
}
ctx.pc += 4; ctx.pc += 4;
return StepResult::SystemCall; return StepResult::SystemCall;
} }
@@ -1733,6 +1743,14 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
// ===== Trap ===== // ===== Trap =====
PpcOpcode::tw | PpcOpcode::twi | PpcOpcode::td | PpcOpcode::tdi => { PpcOpcode::tw | PpcOpcode::twi | PpcOpcode::td | PpcOpcode::tdi => {
// PPCBUG-063: save CIA before incrementing so a trap handler reads
// the faulting instruction address, not CIA+4.
// PPCBUG-065: log the SIMM type code on `twi 31, r0, IMM` (Xbox 360
// typed-trap convention used by the CRT/kernel for C++ exception
// class dispatch). The audit notes this is relevant to the Sylpheed
// throw investigation; routing the type code via a payload requires
// a StepResult enum extension that's deferred for now.
let trap_pc = ctx.pc;
let a = ctx.gpr[instr.ra()]; let a = ctx.gpr[instr.ra()];
let b = match instr.opcode { let b = match instr.opcode {
PpcOpcode::twi | PpcOpcode::tdi => instr.simm16() as i64 as u64, PpcOpcode::twi | PpcOpcode::tdi => instr.simm16() as i64 as u64,
@@ -1743,14 +1761,21 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
_ => trap::TrapWidth::Doubleword, _ => trap::TrapWidth::Doubleword,
}; };
let fired = trap::evaluate(instr.to(), a, b, width); let fired = trap::evaluate(instr.to(), a, b, width);
ctx.pc += 4;
if fired { if fired {
let typed_trap_simm = if matches!(instr.opcode, PpcOpcode::twi)
&& instr.to() == 31 && instr.ra() == 0 {
Some(instr.simm16() as u16)
} else { None };
tracing::warn!( tracing::warn!(
"Trap fired at {:#010x}: {:?} TO={} a={:#x} b={:#x}", "Trap fired at {:#010x}: {:?} TO={} a={:#x} b={:#x}{}",
ctx.pc.wrapping_sub(4), instr.opcode, instr.to(), a, b trap_pc, instr.opcode, instr.to(), a, b,
typed_trap_simm.map_or(String::new(), |t| format!(" typed_trap_simm={:#06x}", t))
); );
// Leave ctx.pc at CIA (NOT NIA) so trap handlers / SEH delivery
// can read the faulting instruction address from ctx.pc.
return StepResult::Trap; return StepResult::Trap;
} }
ctx.pc += 4;
} }
// ===== Byte-reverse loads ===== // ===== Byte-reverse loads =====