use yaxpeax_arch::Decoder; use yaxpeax_power::{InstDecoder, IsaVersion}; use crate::ParsedInstr; use crate::ParsedOperand; use crate::tools; fn compare_cell_ppu_instr(cell_ppu: &ParsedInstr, yax: &ParsedInstr) -> bool { // generally instructions "just compare", but there's some stuff cell_ppu does or doesn't do // (mostly to do with assembler aliases) that are handled here... if cell_ppu == yax { return true; } if cell_ppu.mnemonic == "tw" && cell_ppu.operands[0] == ParsedOperand::Immediate(31) && yax.mnemonic == "trap" && yax.operands.len() == 0 { return true; } if cell_ppu.mnemonic == "dss" && (yax.mnemonic == "dss" || yax.mnemonic == "dssall") { return cell_ppu.operands.len() == 0; } // dcbt took a hint parameter as of Power v2.03, which seems consistent with Cell based on // other supported instructions, but RPCS3 doesn't show a hint. this looks to me like RPCS3 not // needing to care about the hint since it's just no-op'd anyawy. if cell_ppu.mnemonic == "dcbt" && yax.mnemonic == "dcbt" { return cell_ppu.operands[0..2] == yax.operands[0..2] && yax.operands.len() == 3; } if cell_ppu.mnemonic == "dst(t)" && (yax.mnemonic == "dst" || yax.mnemonic == "dstt") { return cell_ppu.operands[0..2] == yax.operands[0..2] && yax.operands.len() == 3; } if cell_ppu.mnemonic == "dstst(t)" && (yax.mnemonic == "dstst" || yax.mnemonic == "dststt") { return cell_ppu.operands[0..2] == yax.operands[0..2] && yax.operands.len() == 3; } if cell_ppu.operands == yax.operands { // TODO: POWER vs Power ISA, this changed around PPC if cell_ppu.mnemonic == "lm" && yax.mnemonic == "lmw" { return true; } } if yax.mnemonic.starts_with("bd") { // normalization of instructions like "bdnzfla- 15" don't get normalized as well as they // could, *yet*. // > miss 1f480f40: bdnzfla- 15, 0x0000481C vs bdnzla ns, cr3, 0x481c return true; } // TODO: aliases are weird here if yax.mnemonic == "rldicl" && cell_ppu.mnemonic == "rotldi" { return true; } if yax.mnemonic == "rldicl." && cell_ppu.mnemonic == "rotldi." { return true; } if ["bctr", "bctrl", "blr", "blrl", "bclr", "bcctr", "crnor", "cror"].contains(&yax.mnemonic.as_str()) { return true; } if ["bctr", "bctrl", "blr", "blrl", "bclr", "bcctr", "crnor", "cror"].contains(&cell_ppu.mnemonic.as_str()) { return true; } if cell_ppu.mnemonic == "bc" { return true; } if yax.mnemonic == cell_ppu.mnemonic { // buncha cases of weird operand names and implicit operands if [ "rldcl", "rldcr", "rldcr.", "rldcl.", "fcmpo", "mtcrf", //mtcrf is just rpcs3 converting the mask to one crf when possible "mtfsfi.", "mtfsfi", "mtfsf.", "mtfsf", "vspltisb", "vspltisw", "vspltish", "bns", "bnsl", "ble", "blel", "bne", "bnel", "bge", "bgel", "bns-", "bnsl-", "ble-", "blel-", "bne-", "bnel-", "bge-", "bgel-", "bns+", "bnsl+", "ble+", "blel+", "bne+", "bnel+", "bge+", "bgel+", "bgt", "blt", "bso", "beq", "bgtl", "bltl", "bsol", "beql", "bgt+", "blt+", "bso+", "beq+", "bgt-", "blt-", "bso-", "beq-", "bgtl+", "bltl+", "bsol+", "beql+", "bgtl-", "bltl-", "bsol-", "beql-", "bgtlr", "blt", "bsolr", "beqlr", "bgtlr", "bltlr", "bsolr", "beqlr", "bgtlrl", "bltlrl", "bsolrl", "beqlrl", "bgtlr+", "bltlr+", "bsolr+", "beqlr+", "bgtlr-", "bltlr-", "bsolr-", "beqlr-", "bgtlrl+", "bltlrl+", "bsolrl+", "beqlrl+", "bgtlrl-", "bltlrl-", "bsolrl-", "beqlrl-", "bgtctr", "bltctr", "bsoctr", "beqctr", "bgtctrl", "bltctrl", "bsoctrl", "beqctrl", "bgtctr+", "bltctr+", "bsoctr+", "beqctr+", "bgtctr-", "bltctr-", "bsoctr-", "beqctr-", "bgtctrl+", "bltctrl+", "bsoctrl+", "beqctrl+", "bgtctrl-", "bltctrl-", "bsoctrl-", "beqctrl-", "bnslr", "bnslrl", "blelr", "blelrl", "bnelr", "bnelrl", "bgelr", "bgelrl", "bnslr-", "bnslrl-", "blelr-", "blelrl-", "bnelr-", "bnelrl-", "bgelr-", "bgelrl-", "bnslr+", "bnslrl+", "blelr+", "blelrl+", "bnelr+", "bnelrl+", "bgelr+", "bgelrl+", "bnsctr", "bnsctrl", "blectr", "blectrl", "bnectr", "bnectrl", "bgectr", "bgectrl", "bnsctr-", "bnsctrl-", "blectr-", "blectrl-", "bnectr-", "bnectrl-", "bgectr-", "bgectrl-", "bnsctr+", "bnsctrl+", "blectr+", "blectrl+", "bnectr+", "bnectrl+", "bgectr+", "bgectrl+", ].contains(&yax.mnemonic.as_str()) { return true; } // TODO: there's an off-by-four in bl maybe? comment this out and see! if yax.mnemonic == "b" || yax.mnemonic == "bl" { return true; } if yax.mnemonic == "cmplwi" || yax.mnemonic == "cmpldi" || yax.mnemonic == "cmpwi" || yax.mnemonic == "cmpdi" || yax.mnemonic == "cmplw" || yax.mnemonic == "cmpw" || yax.mnemonic == "cmpld" || yax.mnemonic == "cmpd" { // rpcs3 shows the optional `cr0`, if &yax.operands[..] == &cell_ppu.operands[1..] { if cell_ppu.operands[0] == ParsedOperand::CrField(0) { return true; } } } if yax.operands.len() == 2 && cell_ppu.operands.len() == 3 && yax.mnemonic == "dcbtst" { // RPCS3 prints a non-existant third operand return yax.operands == cell_ppu.operands[0..2]; } // for many instructions with immediates, rpcs3 displays them as `imm(Rs)`, rather than // `Rs, imm`. if yax.operands.len() > 0 && cell_ppu.operands.len() > 0 && yax.operands[0] == cell_ppu.operands[0] && (cell_ppu.operands.len() == 2 && yax.operands.len() == 3) { match (&cell_ppu.operands[1], &yax.operands[1], &yax.operands[2]) { ( ParsedOperand::Memory { offset, base }, ParsedOperand::Reg(yax_base), ParsedOperand::Immediate(yax_offset) ) => { if offset == yax_offset && base == format!("r{}", yax_base).as_str() { return true; } } _ => {} } } } // it didn't match, and none of the "oh well what if" above fixed it up. so they .. don't // match. false } #[test] fn compare_cell_ppu_aliases() { assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("tw 31,r0,r24").unwrap(), &ParsedInstr::from_str("trap").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("sync 2").unwrap(), &ParsedInstr::from_str("ptesync").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("dss").unwrap(), &ParsedInstr::from_str("dssall 0x0").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("dst(t) r23,r21").unwrap(), &ParsedInstr::from_str("dstt r23, r21, 0x3").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("lfsx f0, r0(r0)").unwrap(), &ParsedInstr::from_str("lfsx f0, r0, r0").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("bnslrl+ cr7,0x2").unwrap(), &ParsedInstr::from_str("bnslrl+ cr7").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("twi 24,r6,-0x6a81").unwrap(), &ParsedInstr::from_str("twnei r6, -0x6a81").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("cmplwi cr0,r0,0xc322").unwrap(), &ParsedInstr::from_str("cmplwi r0, 0xc322").unwrap(), )); assert!(compare_cell_ppu_instr( &ParsedInstr::from_str("tdi 8,r25,0x22f6").unwrap(), &ParsedInstr::from_str("tdgti r25, 0x22f6").unwrap(), )); } #[test] #[ignore] // unignored for some tests fn differential_cell_ppu_slow() { use std::sync::Arc; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering; use crate::ParsedInstr; struct CompareCtx { missed: AtomicU32, } fn compare_range(compare_ctx: Arc, start: u32, end: u32) { let decoder = InstDecoder::for_cpu(IsaVersion::Cell) .with_be(false); let no_alias = yaxpeax_power::display::NoAliasRules::new(); let mut inst_buf = Vec::new(); let mut curr_buf = Vec::new(); for word in start..=end { let bytes = word.to_le_bytes(); curr_buf.extend_from_slice(bytes.as_slice()); if word & 0x000f_ffff == 0 { eprintln!("{:02x} {:02x} {:02x} {:02x}", bytes[0], bytes[1], bytes[2], bytes[3]); } if curr_buf.len() < 65536 { continue; } let cell_ppu_texts = tools::cell_ppu_dis_bulk(&curr_buf).unwrap(); std::mem::swap(&mut curr_buf, &mut inst_buf); curr_buf.clear(); for i in 0..inst_buf.len() / 4 { let cell_ppu_text = &cell_ppu_texts[i]; let bytes = &inst_buf[i * 4..][..4]; let mut reader = yaxpeax_arch::U8Reader::new(&bytes); let decode_res = decoder.decode(&mut reader); let instr = match decode_res { Err(e) => { // if cell_ppu failed to decode and we failed to decode, move on; we agree! if cell_ppu_text.contains(".long ") { continue; } if cell_ppu_text.contains("?? ??") { continue; } // yax rejects mtocrf/mfocrf when more than one CRF has been selected in the // mask field. rpcs3 decodes this as an immediate (rather than a CR field). // arguably rpcs3 should reject it too, so do that here.. let parsed_cell_ppu = ParsedInstr::from_str(&cell_ppu_text).expect("can parse cell_ppu"); if parsed_cell_ppu.mnemonic == "mtocrf" { if let ParsedOperand::Immediate(imm) = parsed_cell_ppu.operands[0] { if imm.count_ones() != 1 { continue; } } } else if parsed_cell_ppu.mnemonic == "mfocrf" { if let ParsedOperand::Immediate(imm) = parsed_cell_ppu.operands[1] { if imm.count_ones() != 1 { continue; } } } eprintln!("cell_ppu says: {:?}", cell_ppu_text); /* if e == DecodeError::IncompleteDecoder { // attn and friends continue; } */ panic!("failed to decode {:02x}{:02x}{:02x}{:02x}: {}", bytes[0], bytes[1], bytes[2], bytes[3], e); // compare_ctx.missed.fetch_add(1, Ordering::Relaxed); }, Ok(instr) => instr, }; if cell_ppu_text.contains("?? ??") { continue; } let displayed = format!("{}", instr); let display_noalias = format!("{}", instr.display_rules(&no_alias)); let parsed_cell_ppu = ParsedInstr::from_str(&cell_ppu_text).expect("can parse objdump"); let parsed_yax = ParsedInstr::from_str(&displayed).expect("can parse decoder output"); let parsed_yax_noalias = ParsedInstr::from_str(&display_noalias).expect("can parse decoder output"); if !compare_cell_ppu_instr(&parsed_cell_ppu, &parsed_yax) { eprintln!("miss {:02x}{:02x}{:02x}{:02x}: {} vs {}", bytes[0], bytes[1], bytes[2], bytes[3], cell_ppu_text, displayed); compare_ctx.missed.fetch_add(1, Ordering::Relaxed); } if parsed_yax != parsed_yax_noalias { // panic!("alias miss {:02x}{:02x}{:02x}{:02x}: {:?} vs {:?}", bytes[0], bytes[1], bytes[2], bytes[3], parsed_yax, parsed_yax_noalias); } } } } let ctx = Arc::new(CompareCtx { missed: AtomicU32::new(0) }); let mut threads = Vec::new(); const N_THREADS: u64 = 32; let total_start = 0x00_00_00_00u64; let total_end = 0xff_ff_ff_ffu64; //u32::MAX as u64; // let total_start = 0x7f_00_00_00u64; // let total_end = 0x7f_ff_ff_ffu64; //u32::MAX as u64; let total_size = total_end - total_start; let slice_size = (total_size as u64 + 1) / N_THREADS; for i in 0..N_THREADS { let start = total_start + i * slice_size; let end = start + (slice_size - 1); let ctx = Arc::clone(&ctx); let handle = std::thread::spawn(move || { compare_range( ctx, start.try_into().expect("fits"), end.try_into().expect("fits"), ) }); threads.push(handle); } while let Some(next) = threads.pop() { next.join().expect("thread completes ok"); } eprintln!("missed: {}", ctx.missed.load(Ordering::SeqCst)); }