chore(audit): mark P4 PPCBUGs applied; append P4 progress section
P4 phase merged at d945aea. Update audit-findings.md status fields
(43 PPCBUGs marked applied) and append the P4 progress section to
audit-report-2026-04-29.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-001 — addi sign-extension, no truncation
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:114-118
|
||||
- **Symptom**: `addi rT, r0, -1` (= `li rT, -1`) writes `0xFFFFFFFF_FFFFFFFF` instead of `0x00000000_FFFFFFFF`. Identical shape to addis.
|
||||
- **Fix**:
|
||||
@@ -41,7 +41,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-002 — addic untruncated writeback + 64-bit CA compare
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:133-140
|
||||
- **Symptom**: (a) GPR writeback not truncated (same shape as addi). (b) CA computed via 64-bit `result < ra` — Canary's `AddDidCarry` explicitly truncates both operands to int32 first.
|
||||
- **Fix**:
|
||||
@@ -56,7 +56,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-003 — addicx untruncated writeback + 64-bit CA + CR0 regression
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:141-150
|
||||
- **Symptom**: same as PPCBUG-002 plus a CR0 regression: live code uses `update_cr_signed(0, result as i64)` (64-bit signed). The frozen snapshot in `ppc-manual/alu/addicx.md` shows the previously-correct `result as i32 as i64` form. Live code has drifted.
|
||||
- **Fix**: PPCBUG-002 fix plus `update_cr_signed(0, result32 as i32 as i64)`.
|
||||
@@ -65,7 +65,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-004 — mulli untruncated 64-bit signed product
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:159-164
|
||||
- **Symptom**: RA read as full `i64`, product stored as `u64` without truncation. Per ISA in 32-bit ABI, both factors should be i32 and product should fit in 32 bits (overflow silently wraps per ISA).
|
||||
- **Fix**:
|
||||
@@ -78,7 +78,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-005 — subficx untruncated writeback + 64-bit CA compare
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:151-158
|
||||
- **Symptom**: (a) `imm.wrapping_sub(ra)` on 64-bit values writes poisoned upper bits; sign-extended `imm` for negative SIMM has bits 32-63 set. (b) CA `imm >= ra` is 64-bit unsigned compare; wrong relative to Canary's 32-bit form.
|
||||
- **Fix**:
|
||||
@@ -93,7 +93,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-006 — negx active GPR poisoning + 64-bit OE overflow check
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:319-330
|
||||
- **Symptom**: (a) `(!ra).wrapping_add(1)` unconditionally sets upper 32 bits to all-ones because `!ra` flips them. Even a clean `r3 = 5` produces `0xFFFFFFFF_FFFFFFFB` instead of `0x00000000_FFFFFFFB`. **This is active, not latent — every neg in 32-bit-ABI code poisons the GPR.** (b) `neg_ov_64` overflow predicate tests `ra == 0x8000_0000_0000_0000` (64-bit INT_MIN) instead of `ra == 0x0000_0000_8000_0000` (32-bit INT_MIN).
|
||||
- **Fix**:
|
||||
@@ -109,7 +109,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-007 — subfcx CA via 64-bit unsigned compare
|
||||
- **Severity**: HIGH (defensive — same shape as the compare that broke addis)
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:258
|
||||
- **Symptom**: `if rb >= ra { 1 } else { 0 }` is the exact 64-bit unsigned compare that the addis bug exploited. Wrong CA when either operand has poisoned upper 32 bits. Apply defensively even if all upstream sources are cleaned, because a wrong CA bit is unrecoverable downstream.
|
||||
- **Fix**:
|
||||
@@ -124,7 +124,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-008 — subfex CA via 64-bit unsigned compare + `!ra` poisons writeback
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:268-284
|
||||
- **Symptom**: (a) CA `if rb > ra || (rb == ra && ca != 0)` is 64-bit; same shape as PPCBUG-007. (b) Writeback uses `(!ra).wrapping_add(rb).wrapping_add(ca)` — `!ra` always sets upper 32 bits, guaranteed GPR poison even with clean inputs (same shape as PPCBUG-006).
|
||||
- **Fix**:
|
||||
@@ -139,7 +139,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-009 — mullwx untruncated 64-bit signed product
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:331-344
|
||||
- **Symptom**: 32x32 multiply produces 64-bit signed `i64` product, written to GPR via `as u64` without truncation. When product overflows i32 (which `mullw_ov` correctly detects), upper 32 bits are non-zero and corrupt downstream 64-bit unsigned compares — same class as addis.
|
||||
- **Fix** (one line; OE handler unchanged):
|
||||
@@ -156,66 +156,66 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-011 — divwx CR0 update breaks after PPCBUG-010 fix
|
||||
- **Severity**: MEDIUM (coupled to PPCBUG-010 — must land together)
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:379
|
||||
- **Symptom**: `update_cr_signed(0, ctx.gpr[instr.rd()] as i64)` accidentally works today because the sign-extended GPR has consistent sign in i64 view. After PPCBUG-010, GPR holds `0x00000000_FFFFFFFD` for `-3` and `as i64` reads positive — CR0.LT will be wrong for negative quotients.
|
||||
- **Fix**: `ctx.update_cr_signed(0, ctx.gpr[instr.rd()] as u32 as i32 as i64);`
|
||||
|
||||
### PPCBUG-012 — addx writeback not truncated (latent)
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:167-179
|
||||
- **Symptom**: 64-bit `wrapping_add` result written to GPR untruncated. Latent: only triggers if upstream operands have poisoned upper 32 bits. With PPCBUG-001 etc. unfixed, that invariant is broken — addx amplifies the poison.
|
||||
- **Fix**: `ctx.gpr[instr.rd()] = result as u32 as u64;`
|
||||
|
||||
### PPCBUG-013 — addcx writeback not truncated (latent)
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:180-193
|
||||
- **Fix**: same shape as PPCBUG-012.
|
||||
|
||||
### PPCBUG-014 — addex writeback not truncated (latent)
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:194-209
|
||||
- **Fix**: same shape as PPCBUG-012.
|
||||
|
||||
### PPCBUG-015 — addzex writeback not truncated (latent)
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:210-224
|
||||
- **Fix**: same shape as PPCBUG-012.
|
||||
|
||||
### PPCBUG-016 — addmex writeback not truncated (latent + edge case)
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:225-240
|
||||
- **Symptom**: same writeback issue plus the `wrapping_sub(1)` produces all-ones upper 32 bits when low 32 bits underflow — guaranteed poison even if inputs are clean (same shape as PPCBUG-006/008).
|
||||
- **Fix**: truncate operands and result to 32 bits.
|
||||
|
||||
### PPCBUG-017 — subfx writeback not truncated (latent)
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:241-253
|
||||
- **Fix**: same shape as PPCBUG-012.
|
||||
|
||||
### PPCBUG-018 — subfzex writeback not truncated + `!ra` poisons
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:285-302
|
||||
- **Symptom**: `(!ra).wrapping_add(ca)` flips upper 32 bits — guaranteed poison.
|
||||
- **Fix**: truncate ra to u32, do arithmetic on u32, write `as u64`.
|
||||
|
||||
### PPCBUG-019 — subfmex writeback poisoning + always-true CA edge
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:303-318
|
||||
- **Symptom**: (a) writeback poisoned via `(!ra)`. (b) CA predicate `(!ra) != 0` is always true when ra has clean upper 32 bits (because `!ra` flips them) — so CA is always 1, even in the documented edge case where 32-bit `ra == 0xFFFFFFFF && ca == 0` should yield CA=0.
|
||||
- **Fix**: operate on u32, then `xer_ca = if (!ra32) != 0 || ca != 0 { 1 } else { 0 }`.
|
||||
|
||||
### PPCBUG-020 — CR0 update uses 64-bit signed compare in all sub-register ops
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Locations**: interpreter.rs:250, 264, 281, 299, 315, 327, 341, 379, 396, 410, 419, 428, 445, 462 (every Rc=1 path in groups 2-5)
|
||||
- **Symptom**: `update_cr_signed(0, result as i64)` views result as 64-bit signed. In 32-bit ABI, bit 31 determines LT/GT, not bit 63. A result like `0x00000000_80000000` is negative in 32-bit but positive in 64-bit — CR0.LT inverted.
|
||||
- **Fix (catch-all)**: change to `result as u32 as i32 as i64` everywhere. Once PPCBUG-001..-019 truncate writebacks, the upper 32 bits of `result` are zero and this distinction becomes moot — but applying both is cheap and provides defense in depth.
|
||||
@@ -237,7 +237,7 @@ Per-group reports: `audit-out/group-01-add-imm.md`, `group-02-add-reg.md`, `grou
|
||||
|
||||
### PPCBUG-023 — andisx CR0 update uses 64-bit signed compare; should use 32-bit
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:475
|
||||
- **Symptom**: `update_cr_signed(0, ctx.gpr[instr.ra()] as i64)` interprets the result as 64-bit signed. The `andisx` result is bounded by `0x0000_0000_FFFF_0000`, which is always non-negative in 64-bit view. In 32-bit ABI, bit 31 is the sign bit — results with bit 31 set (e.g. `andis. rA, rS, 0x8000` with rS=0x80000000 → result=0x80000000) should yield CR0.LT=1, but xenia-rs gives CR0.GT=1. The ppc-manual frozen snapshot for `andisx` shows the correct `as i32 as i64` form; the live code has drifted. Common trigger: `andis. rA, rS, 0x8000` to test the sign bit of a 32-bit word.
|
||||
- **Fix**:
|
||||
@@ -264,7 +264,7 @@ Group 9 summary: core arithmetic is clean — `rlw_mask`, rotate logic, and resu
|
||||
|
||||
### PPCBUG-024 — rlwinmx CR0 update uses 64-bit signed compare; should use 32-bit
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:667
|
||||
- **Symptom**: `update_cr_signed(0, ctx.gpr[instr.ra()] as i64)` — result is a zero-extended u32, so bit 31 set yields +2147483648 in 64-bit signed view but -2147483648 in 32-bit ABI. CR0.LT/GT inverted for results with bit 31 set. `rlwinm.` is the most common dot-form instruction in compiler output (all `slwi.`, `srwi.`, `clrlwi.`, bitfield-test-and-branch idioms).
|
||||
- **Fix**: `ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64);`
|
||||
@@ -272,7 +272,7 @@ Group 9 summary: core arithmetic is clean — `rlw_mask`, rotate logic, and resu
|
||||
|
||||
### PPCBUG-025 — rlwimix CR0 update uses 64-bit signed compare; should use 32-bit
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:679
|
||||
- **Symptom**: same class as PPCBUG-024. `rlwimi.` is compiler-generated for struct bitfield writes; when the inserted value occupies or sets bit 31 of RA, CR0.LT is wrong.
|
||||
- **Fix**: `ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64);`
|
||||
@@ -280,7 +280,7 @@ Group 9 summary: core arithmetic is clean — `rlw_mask`, rotate logic, and resu
|
||||
|
||||
### PPCBUG-026 — rlwnmx CR0 update uses 64-bit signed compare; should use 32-bit
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:690
|
||||
- **Symptom**: same class as PPCBUG-024. `rlwnm.` is less frequent but used in variable-shift normalisation patterns.
|
||||
- **Fix**: `ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64);`
|
||||
@@ -304,7 +304,7 @@ The group 7 subagent also flagged a CR0 regression across all 8 opcodes — that
|
||||
|
||||
### PPCBUG-028 — orcx active GPR poisoning
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:509-513
|
||||
- **Symptom**: writes `rs | !rb`. Rust's `!` on `u64` flips all 64 bits — the upper 32 bits of `!rb` are unconditionally all-ones, OR'd into the result. With clean inputs `orc r5, r3, r4` writes `0xFFFFFFFF_xxxxxxxx`. Active poisoning, same shape as PPCBUG-006/008.
|
||||
- **Fix**: operate on u32, write `as u64`:
|
||||
@@ -316,35 +316,35 @@ The group 7 subagent also flagged a CR0 regression across all 8 opcodes — that
|
||||
|
||||
### PPCBUG-029 — norx active GPR poisoning (the `not` simplified mnemonic)
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:519-523
|
||||
- **Symptom**: writes `!(rs | rb)` — outer `!` flips upper 32 bits unconditionally. **`nor rA, rS, rS` is the canonical `not` simplified mnemonic** used pervasively in PPC code; every `not` in 32-bit-ABI Xbox 360 binaries actively poisons the GPR.
|
||||
- **Fix**: u32 arithmetic, write `as u64`.
|
||||
|
||||
### PPCBUG-030 — nandx active GPR poisoning
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:524-528
|
||||
- **Symptom**: writes `!(rs & rb)` — same shape as norx. The simplified mnemonic `nand` is also `nand rA, rS, rS` (= `nor . . .` in some assemblers).
|
||||
- **Fix**: u32 arithmetic.
|
||||
|
||||
### PPCBUG-031 — eqvx active GPR poisoning
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:529-533
|
||||
- **Symptom**: writes `!(rs ^ rb)` — same shape. The idiom `eqv rA, rS, rS` "set rA to all-ones (i.e. -1 in 32-bit ABI)" produces `0xFFFFFFFF_FFFFFFFF` instead of `0x00000000_FFFFFFFF`.
|
||||
- **Fix**: u32 arithmetic.
|
||||
|
||||
### PPCBUG-032 — andx / orx / xorx writeback not truncated (latent)
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Locations**: interpreter.rs:494-498 (andx), 504-508 (orx), 514-518 (xorx)
|
||||
- **Symptom**: 64-bit bitwise on full GPR values. Latent — clean if both operands are clean; pollutes if either is poisoned upstream.
|
||||
- **Fix**: `as u32 as u64` truncation at writeback. Once all upstream poison sources are fixed, these become unnecessary; until then, defensive truncation.
|
||||
|
||||
### PPCBUG-033 — andcx active poisoning via `!rb` sub-expression
|
||||
- **Severity**: MEDIUM (the `!rb` always poisons; outer `&` masks it away when rs is clean — fully active when rs is poisoned)
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:499-503
|
||||
- **Symptom**: writes `rs & !rb`. The `!rb` always has all-ones upper bits; if rs has clean upper bits (zero), the result is clean. If rs is poisoned upstream, the poison propagates AND the always-set bits in `!rb` make it look "guaranteed". This is closer to active than latent.
|
||||
- **Fix**: `(rs as u32) & !(rb as u32)` then `as u64`.
|
||||
@@ -355,7 +355,7 @@ Per-group report: `audit-out/group-08-extend-clz.md` (report uses local IDs PPCB
|
||||
|
||||
### PPCBUG-034 — extsbx writeback sign-extends to 64 bits
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:537
|
||||
- **Symptom**: `as i8 as i64 as u64` — a byte with high bit set (0x80) writes `0xFFFFFFFF_FFFFFF80` instead of `0x00000000_FFFFFF80`. Active poisoning on every negative byte. `extsb` is emitted by compilers to canonicalize signed-byte arguments — common code path.
|
||||
- **Fix**: `ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i8 as i32 as u32 as u64;`
|
||||
@@ -364,21 +364,21 @@ Per-group report: `audit-out/group-08-extend-clz.md` (report uses local IDs PPCB
|
||||
|
||||
### PPCBUG-035 — extshx writeback sign-extends to 64 bits
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:542
|
||||
- **Symptom**: `as i16 as i64 as u64` — same shape as PPCBUG-034 for halfwords.
|
||||
- **Fix**: `ctx.gpr[instr.ra()] = ctx.gpr[instr.rs()] as i16 as i32 as u32 as u64;`
|
||||
|
||||
### PPCBUG-036 — extsbx CR0 coupling
|
||||
- **Severity**: MEDIUM (must land in same commit as PPCBUG-034)
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:538
|
||||
- **Symptom**: `update_cr_signed(0, ra as i64)` — currently latent because the unfixed sign-extended value's i64 sign matches bit 7 of the byte. After PPCBUG-034 lands, the truncated value's i64 view becomes always non-negative — CR0.LT will never fire for negative byte results.
|
||||
- **Fix**: `ctx.update_cr_signed(0, ctx.gpr[instr.ra()] as u32 as i32 as i64);` — must land with PPCBUG-034.
|
||||
|
||||
### PPCBUG-037 — extshx CR0 coupling
|
||||
- **Severity**: MEDIUM (must land with PPCBUG-035)
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:543
|
||||
- **Symptom**: same coupling shape as PPCBUG-036 for halfwords.
|
||||
|
||||
@@ -425,7 +425,7 @@ Per-group report: `audit-out/group-11-shift.md` (uses local IDs PPCBUG-050..055;
|
||||
|
||||
### PPCBUG-041 — srawx writeback sign-extends to 64 bits
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Locations**: interpreter.rs:583, 588 (two writeback paths for the count<32 and count>=32 branches)
|
||||
- **Symptom**: `result as i64 as u64` violates the 32-bit-ABI zero-extension convention. A negative shifted value writes `0xFFFFFFFF_xxxxxxxx` instead of `0x00000000_xxxxxxxx`.
|
||||
- **Fix**: `result as u32 as u64` in both writeback paths.
|
||||
@@ -433,20 +433,20 @@ Per-group report: `audit-out/group-11-shift.md` (uses local IDs PPCBUG-050..055;
|
||||
|
||||
### PPCBUG-042 — srawix writeback sign-extends to 64 bits
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Locations**: interpreter.rs:600, 605 (same shape as PPCBUG-041 for srawi)
|
||||
- **Fix**: `result as u32 as u64`.
|
||||
|
||||
### PPCBUG-043 — srawx / srawix CR0 coupling
|
||||
- **Severity**: MEDIUM (must land with PPCBUG-041 and PPCBUG-042)
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Locations**: interpreter.rs:593, 607
|
||||
- **Symptom**: currently masked by the sign-extended writeback (sign-extension makes the 64-bit and 32-bit sign agree). After truncating the writeback, `as i64` will misread the sign for negative results.
|
||||
- **Fix**: `as u32 as i32 as i64` in both Rc=1 paths, applied with PPCBUG-041/042.
|
||||
|
||||
### PPCBUG-044 — slwx / srwx CR0 misclassifies negative 32-bit results
|
||||
- **Severity**: LOW (zero-extended results have bit 31 set in low 32, but always positive in i64 view → CR0.LT never fires for slw/srw with bit-31-set results)
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Locations**: interpreter.rs:568, 576
|
||||
- **Fix**: `as u32 as i32 as i64`.
|
||||
|
||||
@@ -994,7 +994,7 @@ and zero unit tests for all nine opcodes.
|
||||
|
||||
### PPCBUG-095 — `lha`: GPR writeback sign-extends to 64 bits
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:990
|
||||
- **Symptom**: `mem.read_u16(ea) as i16 as i64 as u64` — memory `0x8000` writes
|
||||
`0xFFFFFFFF_FFFF8000` instead of `0x00000000_FFFF8000`. Active GPR poisoning for every
|
||||
@@ -1008,7 +1008,7 @@ and zero unit tests for all nine opcodes.
|
||||
|
||||
### PPCBUG-096 — `lhax`: GPR writeback sign-extends to 64 bits
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:996
|
||||
- **Symptom**: identical to PPCBUG-095. Indexed form emitted for array access with GPR index.
|
||||
- **Fix**: `mem.read_u16(ea) as i16 as i32 as u32 as u64`
|
||||
@@ -1016,7 +1016,7 @@ and zero unit tests for all nine opcodes.
|
||||
|
||||
### PPCBUG-097 — `lhau`: GPR writeback sign-extends to 64 bits
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:1007
|
||||
- **Symptom**: identical to PPCBUG-095. Update form emitted for auto-incrementing `short[]` loops;
|
||||
poison accumulates across all iterations.
|
||||
@@ -1025,7 +1025,7 @@ and zero unit tests for all nine opcodes.
|
||||
|
||||
### PPCBUG-098 — `lhaux`: GPR writeback sign-extends to 64 bits
|
||||
- **Severity**: HIGH
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Location**: interpreter.rs:1013
|
||||
- **Symptom**: identical to PPCBUG-095, update+indexed form.
|
||||
- **Fix**: `mem.read_u16(ea) as i16 as i32 as u32 as u64`
|
||||
@@ -1072,7 +1072,7 @@ any store instruction, breaking multi-threaded `lwarx`/`stwcx.` atomicity under
|
||||
### PPCBUG-105 — lwa / lwax / lwaux sign-extend to 64 bits; 32-bit-ABI hazard
|
||||
|
||||
- **Severity**: MEDIUM
|
||||
- **Status**: open
|
||||
- **Status**: applied (P4 d945aea, 2026-05-02)
|
||||
- **Locations**: interpreter.rs:1032 (lwa), 1038 (lwax), 1043 (lwaux)
|
||||
- **Symptom**: `mem.read_u32(ea) as i32 as i64 as u64` — a word with high bit set (e.g. `0x8000_0000`)
|
||||
writes `0xFFFF_FFFF_8000_0000` to rD. ISA-correct for 64-bit-mode `lwa`. In 32-bit ABI, the poisoned
|
||||
|
||||
@@ -365,6 +365,41 @@ After applying Phase 1 alone, run `xenia-rs check sylpheed.iso -n 4B --parallel`
|
||||
|
||||
---
|
||||
|
||||
### P4 — 32-bit ABI writeback truncation sweep (merged 2026-05-02, HEAD d945aea)
|
||||
|
||||
**PPCBUGs fixed**: ~43 IDs across the 4a/4b/4c/4d sub-sections.
|
||||
- 4a active poisoning: 006 (negx), 008 (subfex), 018 (subfzex), 019 (subfmex), 028 (orcx), 029 (norx), 030 (nandx), 031 (eqvx), 033 (andcx)
|
||||
- 4a/4d coupled: 034+035+036+037 (extsbx/extshx writeback + CR0)
|
||||
- 4b immediate ALU: 001 (addi), 002 (addic), 003 (addicx), 004 (mulli), 005 (subficx), 007 (subfcx CA)
|
||||
- 4b mul/div + srawx coupled: 009 (mullwx), 010+011 (divwx + CR0), 041+042+043 (srawx/srawix + CR0)
|
||||
- 4b loads: 095-098 (lha/lhax/lhau/lhaux), 105 (lwa/lwax/lwaux)
|
||||
- 4c latent: 012-017 (addx/addcx/addex/addzex/addmex/subfx), 032 (andx/orx/xorx CR0)
|
||||
- 4d CR0 catch-all: 020 (in mulhwx/mulhwux/divwux/andx/orx/xorx/cntlzwx etc.), 023 (andisx), 024 (rlwinmx), 025 (rlwimix), 026 (rlwnmx), 044 (slwx/srwx)
|
||||
|
||||
**Batches**:
|
||||
- Batch 1 (e18a0a4): 4a active poisoning NOT/SUB family — 9 PPCBUGs
|
||||
- Batch 2 (145a7a4): 4a/4d coupled extsbx+extshx+CR0 — 4 PPCBUGs (must land together)
|
||||
- Batch 3 (bf8208e): 4b immediate ALU — 6 PPCBUGs
|
||||
- Batch 4 (82a9bff): 4b mul/div + srawx coupled — 6 PPCBUGs (two coupling groups)
|
||||
- Batch 5 (20a730d): 4b halfword + lwa loads — 5 PPCBUGs
|
||||
- Batch 6 (16993bb): 4c latent + 4d CR0 catch-all — ~13 PPCBUGs
|
||||
- Review-fix (49103bb): subfx/subfcx OE predicate + mulli test rigor
|
||||
|
||||
**Phase invariants restored**: every 32-bit ABI GPR write zero-extends from a u32 result, every CR0 update views the result as i32, every CA bit comes from a 32-bit unsigned compare. Downstream 64-bit unsigned compares (the addis-incident shape) can no longer be fed polluted upper bits from any of the 40+ touched ALU sites. The frozen-snapshot drift detected in PPCBUG-003 (addicx CR0) and PPCBUG-023 (andisx CR0) is also resolved.
|
||||
|
||||
**Review findings**:
|
||||
- BLOCKING issue caught: subfx and subfcx OE handlers in batch 6 still used the legacy `sum_overflow_64` helper. The helper compares the 32-bit `true_diff` against a u64 view of the result; any legitimate i32::MIN result (bit 31 set) spuriously triggered OV=1. Fixed in 49103bb with two new discriminating regression tests.
|
||||
- Minor caught: `mulli_overflow_wraps_to_32` rubber-stamped — both pre/post fix wrote 0 for the chosen inputs. Redesigned to use polluted-upper-bits inputs that genuinely discriminate.
|
||||
|
||||
**Gate results**:
|
||||
- `cargo test --workspace --release`: **494 passed, 0 failed** (up from 470 at P3 merge; 24 new regression tests across the batches)
|
||||
- 64-bit ABI ops verified untouched: rldicl/rldicr/rldic/rldimi/rldcl/rldcr, sldx/srdx/sradx/sradix, mulhdx/mulhdux/mulldx, divdx/divdux, cntlzdx, extswx
|
||||
- **Acid test** `-n 4B --parallel --reservations-table`: deferred per user direction
|
||||
|
||||
**Conclusion**: P4 is the largest ABI-correctness sweep of the audit. The systemic invariant is restored. Next: P5 — FPU correctness (~30 IDs).
|
||||
|
||||
---
|
||||
|
||||
## Index — every PPCBUG referenced (in numerical order)
|
||||
|
||||
This list intentionally includes every ID found in `audit-findings.md` so nothing is dropped. For each entry's full description / file:line / fix snippet / test recommendation, see the corresponding `### PPCBUG-NNN` heading in `audit-findings.md`.
|
||||
|
||||
Reference in New Issue
Block a user