fix(cpu): PPCBUG-040 PPCBUG-560 fix sh64() bit order and rldicl test helper
PPCBUG-040: decoder.rs sh64() assembled the XS-form shift amount as (SH[4:0] << 1) | SH[5] instead of (SH[5] << 5) | SH[4:0]. Every `sradi` with shift N ∈ 1..=62 executed with a completely wrong shift count (e.g. shift=32 executed as shift=1). PPCBUG-560: disasm_goldens.rs rldicl() test helper was encoding sh[5:1] at PPC bits 16-20 and sh[0] at PPC bit 30 — exactly backwards. The wrong encoder and wrong decoder cancelled out, hiding PPCBUG-040 from tests. Fix both together so tests validate ISA-correct encodings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,7 +89,7 @@ impl DecodedInstr {
|
|||||||
|
|
||||||
/// SH field for 64-bit shifts (bits 16-20 + bit 30)
|
/// SH field for 64-bit shifts (bits 16-20 + bit 30)
|
||||||
#[inline] pub fn sh64(&self) -> u32 {
|
#[inline] pub fn sh64(&self) -> u32 {
|
||||||
(extract_bits(self.raw, 16, 20) << 1) | extract_bits(self.raw, 30, 30)
|
(extract_bits(self.raw, 30, 30) << 5) | extract_bits(self.raw, 16, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SPR field (bits 11-20, swapped halves)
|
/// SPR field (bits 11-20, swapped halves)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use xenia_cpu::decoder::decode;
|
use xenia_cpu::decoder::{DecodedInstr, decode};
|
||||||
use xenia_cpu::disasm::format;
|
use xenia_cpu::disasm::format;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
@@ -158,20 +158,20 @@ fn rlwinm(rs: u32, ra: u32, sh: u32, mb: u32, me: u32, rc: u32) -> u32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn rldicl(rs: u32, ra: u32, sh: u32, mb: u32, rc: u32) -> u32 {
|
fn rldicl(rs: u32, ra: u32, sh: u32, mb: u32, rc: u32) -> u32 {
|
||||||
// MD-form, op30 xo=0. sh split: bits 16-20 (high 5) + bit 30 (low bit).
|
// MD-form: sh[4:0] at PPC bits 16-20 (host bits 11-15); sh[5] at PPC bit 30 (host bit 1).
|
||||||
// mb split: bits 21-25 (low 5) + bit 26 (high bit).
|
// mb[4:0] at PPC bits 21-25 (host bits 6-10); mb[5] at PPC bit 26 (host bit 5).
|
||||||
let sh_hi = (sh >> 1) & 0x1F;
|
let sh_lo = sh & 0x1F;
|
||||||
let sh_lo = sh & 1;
|
let sh_hi = (sh >> 5) & 1;
|
||||||
let mb_lo = mb & 0x1F;
|
let mb_lo = mb & 0x1F;
|
||||||
let mb_hi = (mb >> 5) & 1;
|
let mb_hi = (mb >> 5) & 1;
|
||||||
(30 << 26)
|
(30 << 26)
|
||||||
| (rs << 21)
|
| (rs << 21)
|
||||||
| (ra << 16)
|
| (ra << 16)
|
||||||
| (sh_hi << 11)
|
| (sh_lo << 11)
|
||||||
| (mb_lo << 6)
|
| (mb_lo << 6)
|
||||||
| (mb_hi << 5)
|
| (mb_hi << 5)
|
||||||
| (0 << 2)
|
| (0 << 2)
|
||||||
| (sh_lo << 1)
|
| (sh_hi << 1)
|
||||||
| rc
|
| rc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,3 +529,26 @@ fn vmx128_registers() {
|
|||||||
all.extend_from_slice(&vmx128_4op);
|
all.extend_from_slice(&vmx128_4op);
|
||||||
assert_or_regen("vmx128_registers.json", &all);
|
assert_or_regen("vmx128_registers.json", &all);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sradi_shift_32_decodes_to_32() {
|
||||||
|
// sradi rA, rS, 32: sh=32 → sh[4:0]=0, sh[5]=1
|
||||||
|
// After PPCBUG-040 fix, sh64() must return 32, not 1.
|
||||||
|
let instr: DecodedInstr = decode(rldicl(3, 4, 32, 63, 0), 0);
|
||||||
|
// rldicl with mb=63 is not sradi, but tests sh64() extraction.
|
||||||
|
assert_eq!(instr.sh64(), 32, "sh64 must return 32 for sh=32 (sh5=1, sh_lo=0)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sh64_shift_1_decodes_correctly() {
|
||||||
|
// sh=1: sh[4:0]=1, sh[5]=0 → sh64() must return 1
|
||||||
|
let instr: DecodedInstr = decode(rldicl(3, 4, 1, 0, 0), 0);
|
||||||
|
assert_eq!(instr.sh64(), 1, "sh64 must return 1 for sh=1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sh64_shift_63_decodes_correctly() {
|
||||||
|
// sh=63: sh[4:0]=31=0x1F, sh[5]=1 → sh64() must return 63
|
||||||
|
let instr: DecodedInstr = decode(rldicl(3, 4, 63, 0, 0), 0);
|
||||||
|
assert_eq!(instr.sh64(), 63, "sh64 must return 63 for sh=63");
|
||||||
|
}
|
||||||
|
|||||||
@@ -182,7 +182,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "srdi r3, r4, 8",
|
"label": "srdi r3, r4, 8",
|
||||||
"raw": "0x7883E200",
|
"raw": "0x7883C202",
|
||||||
"addr": "0x82000000",
|
"addr": "0x82000000",
|
||||||
"mnemonic": "rldicl",
|
"mnemonic": "rldicl",
|
||||||
"operands": "r3, r4, 56, 8",
|
"operands": "r3, r4, 56, 8",
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "rotldi r3, r4, 8",
|
"label": "rotldi r3, r4, 8",
|
||||||
"raw": "0x78832000",
|
"raw": "0x78834000",
|
||||||
"addr": "0x82000000",
|
"addr": "0x82000000",
|
||||||
"mnemonic": "rldicl",
|
"mnemonic": "rldicl",
|
||||||
"operands": "r3, r4, 8, 0",
|
"operands": "r3, r4, 8, 0",
|
||||||
|
|||||||
Reference in New Issue
Block a user