fix(cpu): SWAPBUG-001 — revert addi 32-bit truncation
The addi opcode was truncating its result to 32 bits per the post-P4-batch3
"32-bit ABI" rationale (commit bf8208e). Hunk-level bisection during the
2026-05 audit (M11) isolated this single cast as the cause of the
post-P8 swap regression: swaps dropped 2 → 1 and the renderer lost a
frame. PowerISA mandates sign-extension to 64 bits; canary does not
truncate addi. The truncation was a canary-divergent over-extension
of the addis fix (which IS canary-divergent by design, see
addis at interpreter.rs:121-134).
The addi_li_neg_one_zero_extends_upper test encoded the wrong invariant.
Replaced with a sign-extension test asserting canonical PowerISA
behavior (gpr[3] == 0xFFFF_FFFF_FFFF_FFFF for `li r3, -1`).
Verification at -n 100M lockstep:
swaps: 1 → 2 (gate met)
draws: 0 → 0 (unchanged — gated by Phase C+D+E)
instructions: ~100M (unchanged)
imports: 11.4M → 987k (game escapes retry loop)
packets: 281M → 57M (same)
interrupts_delivered: 629 → 630
Tests: 551 passing (unchanged). Lockstep determinism: byte-identical
across two 100M runs except packets (±5%, GPU-thread-race noise floor).
Closes SWAPBUG-001 / PPCBUG-001.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -112,10 +112,8 @@ fn execute(ctx: &mut PpcContext, mem: &dyn MemoryAccess, instr: &DecodedInstr) -
|
||||
match instr.opcode {
|
||||
// ===== ALU: Immediate =====
|
||||
PpcOpcode::addi => {
|
||||
// PPCBUG-001: 32-bit ABI. `li rT, -1` (= addi rT, r0, -1) must produce
|
||||
// 0x00000000_FFFFFFFF, not 0xFFFFFFFF_FFFFFFFF (sign-extended simm16).
|
||||
let ra_val = if instr.ra() == 0 { 0 } else { ctx.gpr[instr.ra()] };
|
||||
ctx.gpr[instr.rd()] = ra_val.wrapping_add(instr.simm16() as i64 as u64) as u32 as u64;
|
||||
ctx.gpr[instr.rd()] = ra_val.wrapping_add(instr.simm16() as i64 as u64);
|
||||
ctx.pc += 4;
|
||||
}
|
||||
PpcOpcode::addis => {
|
||||
@@ -5570,9 +5568,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn addi_li_neg_one_zero_extends_upper() {
|
||||
// PPCBUG-001: `li r3, -1` (= addi r3, r0, -1) must produce
|
||||
// 0x00000000_FFFFFFFF, not 0xFFFFFFFF_FFFFFFFF.
|
||||
fn addi_li_neg_one_sign_extends_per_powerisa() {
|
||||
// SWAPBUG-001 / PPCBUG-001 revert: `li r3, -1` (= addi r3, r0, -1)
|
||||
// must sign-extend simm16 to 64 bits per PowerISA, producing
|
||||
// 0xFFFFFFFF_FFFFFFFF. The pre-revert form truncated to 32 bits,
|
||||
// which broke the swap path (canary-divergent and load-bearing).
|
||||
let mut ctx = PpcContext::new();
|
||||
let mut mem = TestMem::new();
|
||||
// addi r3, r0, -1: opcode 14, simm16 = 0xFFFF
|
||||
@@ -5580,7 +5580,7 @@ mod tests {
|
||||
write_instr(&mut mem, 0, raw);
|
||||
ctx.pc = 0;
|
||||
step(&mut ctx, &mut mem);
|
||||
assert_eq!(ctx.gpr[3], 0x0000_0000_FFFF_FFFFu64);
|
||||
assert_eq!(ctx.gpr[3], 0xFFFF_FFFF_FFFF_FFFFu64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user