diff --git a/crates/xenia-cpu/src/interpreter.rs b/crates/xenia-cpu/src/interpreter.rs index 2abc218..497d22f 100644 --- a/crates/xenia-cpu/src/interpreter.rs +++ b/crates/xenia-cpu/src/interpreter.rs @@ -556,13 +556,17 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) - // ===== Extend/Count ===== PpcOpcode::extsbx => { - ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i8 as i64 as u64; - if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); } + // PPCBUG-034: 32-bit ABI — sign-extend byte to i32, write zero-extended. + // PPCBUG-036 (coupled): CR0 must view result as i32, not i64. + ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i8 as i32 as u32 as u64; + if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); } ctx.pc += 4; } PpcOpcode::extshx => { - ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i16 as i64 as u64; - if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as i64); } + // PPCBUG-035: same shape as extsbx for halfwords. + // PPCBUG-037 (coupled): CR0 i32 view. + ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i16 as i32 as u32 as u64; + if instr.rc_bit() { ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64); } ctx.pc += 4; } PpcOpcode::extswx => { @@ -5182,6 +5186,39 @@ mod tests { assert_eq!(ctx.xer_ca, 1, "rb>=ra → CA=1 (10 > 5)"); } + #[test] + fn extsbx_negative_byte_zero_extends_upper() { + // PPCBUG-034+036 coupled: extsb of 0x80 (negative byte) must produce + // 0x00000000_FFFFFF80, NOT 0xFFFFFFFF_FFFFFF80. CR0.LT must still fire + // (i32 view of 0xFFFFFF80 is negative). + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.gpr[3] = 0x80; + // extsbx. r5, r3 (XO=954, Rc=1) + let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (954 << 1) | 1; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_FF80); + assert!(ctx.cr[0].lt, "CR0.LT must fire for negative i32"); + assert!(!ctx.cr[0].gt); + } + + #[test] + fn extshx_negative_halfword_zero_extends_upper() { + // PPCBUG-035+037 coupled: extsh of 0x8000 must produce 0x00000000_FFFF8000. + let mut ctx = PpcContext::new(); + let mut mem = TestMem::new(); + ctx.gpr[3] = 0x8000; + // extshx. r5, r3 (XO=922, Rc=1) + let raw = (31u32 << 26) | (3 << 21) | (5 << 16) | (922 << 1) | 1; + write_instr(&mut mem, 0, raw); + ctx.pc = 0; + step(&mut ctx, &mut mem); + assert_eq!(ctx.gpr[5], 0x0000_0000_FFFF_8000); + assert!(ctx.cr[0].lt); + } + #[test] fn subfmex_ra_max_ca_zero_clears_ca() { // PPCBUG-019: `subfme` with RA=u32::MAX and CA=0 should set CA=0