use yaxpeax_arch::Decoder; use yaxpeax_power::{InstDecoder, IsaVersion}; use crate::ParsedInstr; use crate::ParsedOperand; use crate::tools; fn compare_xenia_instr(xenia: &ParsedInstr, yax: &ParsedInstr) -> bool { // generally instructions "just compare", but there's some stuff xenia does or doesn't do // (mostly to do with assembler aliases) that are handled here... if xenia == yax { return true; } if xenia.operands == yax.operands { // TODO: POWER vs Power ISA, this changed around PPC if xenia.mnemonic == "lm" && yax.mnemonic == "lmw" { return true; } } if xenia.mnemonic == yax.mnemonic { static BRS: &'static [&'static str] = &[ "b", "bl", ]; static BCS: &'static [&'static str] = &[ "bc", "bcl", ]; // xenia does not print operands for these, though both have operands. so we can only check // the mnemonics match here.. static XENIA_NO_OP_VMX128: &[&'static str] = &[ "vpkd3d128", "vupkd3d128", ]; if XENIA_NO_OP_VMX128.contains(&xenia.mnemonic.as_ref()) { return true; } if BRS.contains(&xenia.mnemonic.as_ref()) { match (&xenia.operands[0], &yax.operands[0]) { // xenia prints branch destinations as addresses even for relative branches: // 917b0048: bl 0x7B94 (ParsedOperand::Immediate(left), ParsedOperand::RelBranch(right)) => { if (*left as i32) == right.wrapping_add(4) as i32 { return true; } } _ => {} } } else if BCS.contains(&xenia.mnemonic.as_ref()) { if xenia.operands[0..2] == yax.operands[0..2] { match (&xenia.operands[2], &yax.operands[2]) { // xenia prints branch destinations as addresses even for relative branches: // 817e0240: bcl 0, 2, 0x7E84 (ParsedOperand::Immediate(left), ParsedOperand::RelBranch(right)) => { if (*left as i32) == right.wrapping_add(4) as i32 { return true; } } _ => {} } } } else if xenia.mnemonic == "mtfsfi" || xenia.mnemonic == "mtfsfi." { // xenia prints the unshifted immediate, but the manual notes that the low bit is // not part of the immediate so yax appears shifted right by 1. match (&xenia.operands[0], &yax.operands[0]) { (ParsedOperand::CrField(xenia_crf), ParsedOperand::FpscrField(yax_crf)) => { // and now yax has a different operand type for FPSCR fields to distincuish the // general-purpose CR accesses... return xenia_crf == yax_crf; } _ => { return false; } } } else if xenia.mnemonic == "sc" { // xenia does not print the call number if it is 0 if yax.operands[0] == ParsedOperand::Immediate(0) && xenia.operands.len() == 0 { return true; } } else if xenia.mnemonic == "vspltb" { // xenia does not truncate the immediate as the manual specifies, at least in the // displayed instruction. if yax.operands[0..2] == xenia.operands[0..2] { if let (ParsedOperand::Immediate(yax_imm), ParsedOperand::Immediate(xenia_imm)) = (yax.operands[2].clone(), xenia.operands[2].clone()) { if yax_imm == xenia_imm & 0x0f { return true; } } } } } if xenia.mnemonic == "mtspr" { // xenia does not swap the low/high 5 bits of the encoded SPR number, but unfortunately // they are stored swapped from the order they are interpreted as. decode that here and // compare, as yax reports the unscrombled version. if let ParsedOperand::Immediate(xenia_spr) = xenia.operands[0] { let unscrambled = ((xenia_spr & 0x1f) << 5) | (xenia_spr >> 5); match unscrambled { 1 => { return yax.mnemonic == "mtxer" && yax.operands.len() == 1 && xenia.operands[1] == yax.operands[0]; } 8 => { return yax.mnemonic == "mtlr" && yax.operands.len() == 1 && xenia.operands[1] == yax.operands[0]; } 9 => { return yax.mnemonic == "mtctr" && yax.operands.len() == 1 && xenia.operands[1] == yax.operands[0]; } other => { return yax.mnemonic == "mtspr" && yax.operands.len() == 2 && yax.operands[0] == ParsedOperand::Spr(other.try_into().unwrap()) && yax.operands[1] == xenia.operands[1]; } } } } else if xenia.mnemonic == "mfspr" { // xenia does not swap the low/high 5 bits of the encoded SPR number, but unfortunately // they are stored swapped from the order they are interpreted as. decode that here and // compare, as yax reports the unscrombled version. if let ParsedOperand::Immediate(xenia_spr) = xenia.operands[1] { let unscrambled = ((xenia_spr & 0x1f) << 5) | (xenia_spr >> 5); match unscrambled { 1 => { return yax.mnemonic == "mfxer" && yax.operands.len() == 1 && xenia.operands[0] == yax.operands[0]; } 8 => { return yax.mnemonic == "mflr" && yax.operands.len() == 1 && xenia.operands[0] == yax.operands[0]; } 9 => { return yax.mnemonic == "mfctr" && yax.operands.len() == 1 && xenia.operands[0] == yax.operands[0]; } other => { return yax.mnemonic == "mfspr" && yax.operands.len() == 2 && yax.operands[1] == ParsedOperand::Spr(other.try_into().unwrap()) && yax.operands[0] == xenia.operands[0]; } } } } else if xenia.mnemonic == "mfcr" && yax.mnemonic == "mfocrf" { // xenia does not print the alias or make `FXM` available, so all we can do is compare the // first operand. return xenia.operands[0] == yax.operands[0]; } else if xenia.mnemonic == "mtcrf" && yax.mnemonic == "mtocrf" { // xenia does not print the alias or make `FXM` available, so all we can do is compare the // first operand. return xenia.operands[1] == yax.operands[1]; } // some Xenia instructions do not have rules to print operands, even though they // .. have operands. this should probably be fixed over there.. // // hint: search for `, nullptr),` in `ppc_opcode_disasm_gen.cc` static XENIA_NO_OPERANDS: &'static [&'static str] = &[ "lmw", "lswi", "lswx", "mfvscr", "mtvscr", "stmw", "stswi", "swswx", "stswx", "vpkd3d128", "vupkded138", ]; fn compare_cmp_operands(xenia: &ParsedInstr, yax: &ParsedInstr) -> bool { if yax.operands.len() == 2 { xenia.operands[0] == ParsedOperand::CrField(0) && &xenia.operands[2..] == &yax.operands[0..] } else { xenia.operands[0] == yax.operands[0] && &xenia.operands[2..] == &yax.operands[1..] } } if xenia.mnemonic == "cmpi" { // xenia does not print cmpwi/cmpdi based on L like the assembler mnemonics in // the book indicate. // first, compare eeverything else if (xenia.operands[1] == ParsedOperand::Immediate(0) && yax.mnemonic == "cmpwi") || (xenia.operands[1] == ParsedOperand::Immediate(1) && yax.mnemonic == "cmpdi") { return compare_cmp_operands(xenia, yax); } } else if xenia.mnemonic == "cmpli" { // xenia does not print cmplwi/cmpldi based on L like the assembler mnemonics in // the book indicate. // first, compare eeverything else if (xenia.operands[1] == ParsedOperand::Immediate(0) && yax.mnemonic == "cmplwi") || (xenia.operands[1] == ParsedOperand::Immediate(1) && yax.mnemonic == "cmpldi") { return compare_cmp_operands(xenia, yax); } } else if xenia.mnemonic == "cmp" { // xenia does not print cmpw/cmpd based on L like the assembler mnemonics in // the book indicate. // first, compare eeverything else if (xenia.operands[1] == ParsedOperand::Immediate(0) && yax.mnemonic == "cmpw") || (xenia.operands[1] == ParsedOperand::Immediate(1) && yax.mnemonic == "cmpd") { return compare_cmp_operands(xenia, yax); } } else if xenia.mnemonic == "addis" { if yax.mnemonic == "subis" && xenia.operands[..2] == yax.operands[..2] { match (&xenia.operands[2], &yax.operands[2]) { (ParsedOperand::Immediate(l), ParsedOperand::Immediate(r)) => { if *l == -*r { return true; } } _ => { // no match, fall through to record miss } } } else if yax.mnemonic == "lis" { if xenia.operands[0] == yax.operands[0] && xenia.operands[2] == yax.operands[1] && xenia.operands[1] == ParsedOperand::Reg(0) { // add to reg with source reg r0 aliases to `lis` by Power ISA manuals, but xenia // doesn't bother with aliaes. return true; } } } else if xenia.mnemonic == "addi" { if yax.mnemonic == "subi" && xenia.operands[..2] == yax.operands[..2] { match (&xenia.operands[2], &yax.operands[2]) { (ParsedOperand::Immediate(l), ParsedOperand::Immediate(r)) => { if *l == -*r { return true; } } _ => { // no match, fall through to record miss } } } else if yax.mnemonic == "li" { if xenia.operands[0] == yax.operands[0] && xenia.operands[2] == yax.operands[1] && xenia.operands[1] == ParsedOperand::Reg(0) { // add to reg with source reg r0 aliases to `li` by Power ISA manuals, but xenia // doesn't bother with aliaes. return true; } } } else if xenia.mnemonic == "tw" { static COND_CODES: &'static [(&'static str, u8)] = &[ ("twi", 0), ("twlti", 1), ("twgti", 2), ("twnei", 3), ("tweqi", 4), ("twlgei", 5), ("twllei", 6), ("twi", 7), ("twi", 8), ("twi", 9), ("twi", 10), ("twi", 11), ("twi", 12), ("twi", 13), ("twi", 14), ("twi", 15), ("twi", 16), ("twi", 17), ("twi", 18), ("twi", 19), ("twi", 20), ("twi", 21), ("twi", 22), ("twi", 23), ("twnei", 24), ("twi", 25), ("twi", 26), ("twi", 27), ("twi", 28), ("twi", 29), ("twi", 30), ("twi", 31), ]; if yax.mnemonic == "twi" { // xenia just disagrees about the mnemonic but.. this is opcode 3 and has an immediate.. return xenia.operands == yax.operands; } else { for (op, code) in COND_CODES.iter() { if yax.mnemonic.as_str() == *op && xenia.operands[0] == ParsedOperand::Immediate(*code as i64) && xenia.operands[1..] == yax.operands[0..] { return true; } } } } else if xenia.mnemonic == "tdi" { static COND_CODES: &'static [(&'static str, u8)] = &[ ("tdi", 0), ("tdlti", 1), ("tdgti", 2), ("tdnei", 3), ("tdeqi", 4), ("tdlgei", 5), ("tdllei", 6), ("tdi", 7), ("tdi", 8), ("tdi", 9), ("tdi", 10), ("tdi", 11), ("tdi", 12), ("tdi", 13), ("tdi", 14), ("tdi", 15), ("tdi", 16), ("tdi", 17), ("tdi", 18), ("tdi", 19), ("tdi", 20), ("tdi", 21), ("tdi", 22), ("tdi", 23), ("tdnei", 24), ("tdi", 25), ("tdi", 26), ("tdi", 27), ("tdi", 28), ("tdi", 29), ("tdi", 30), ("tdi", 31), ]; if yax.mnemonic == "tdi" { // xenia just disagrees about the mnemonic but.. this is opcode 3 and has an immediate.. return xenia.operands == yax.operands; } else { for (op, code) in COND_CODES.iter() { if yax.mnemonic.as_str() == *op && xenia.operands[0] == ParsedOperand::Immediate(*code as i64) && xenia.operands[1..] == yax.operands[0..] { return true; } } } } else if xenia.mnemonic == yax.mnemonic && ["vspltisb", "vspltish", "vspltisw"].contains(&yax.mnemonic.as_str()) { if yax.operands[0] != xenia.operands[0] { return false; } if let (ParsedOperand::Immediate(xenia_imm), ParsedOperand::Immediate(yax_imm)) = ( xenia.operands[1].clone(), yax.operands[1].clone() ) { // xenia doesn't sign-extend the immediate, yax does. let xenia_sim = ((xenia_imm as i8) << 3) >> 3; if xenia_sim as i64 == yax_imm { return true; } } } else if XENIA_NO_OPERANDS.contains(&xenia.mnemonic.as_str()) && xenia.mnemonic == yax.mnemonic { // TODO: should really go fix xenia to print these operands (so we can parse // them!) but.. 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_xenia_aliases() { assert!(compare_xenia_instr( &ParsedInstr::from_str("mfspr r0, 983").unwrap(), &ParsedInstr::from_str("mfspr r0, spr766").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("cmpi crf4, 1, r3, -0x5981").unwrap(), &ParsedInstr::from_str("cmpdi cr4, r3, -22913").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("cmpi crf4, 0, r25, 0x7F7D").unwrap(), &ParsedInstr::from_str("cmpwi cr4, r25, 0x7f7d").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("cmpli crf0, 0, r0, 0x2DED").unwrap(), &ParsedInstr::from_str("cmplwi r0, 0x2ded").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("addi r18, r0, -0x5A14").unwrap(), &ParsedInstr::from_str("li r18, -23060").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("addis r0, r0, 0x7681").unwrap(), &ParsedInstr::from_str("lis r0, 30337").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("addi r17, r8, -0x7f14").unwrap(), &ParsedInstr::from_str("subi r17, r8, 32532").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("addi r17, r8, -0x7f14").unwrap(), &ParsedInstr::from_str("subi r17, r8, 32532").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("bcla 2, 5, 0xFFFFF248").unwrap(), &ParsedInstr::from_str("bcla 2, 5, 0xFFFFF248").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("bcl 0, 2, 0x7E84").unwrap(), &ParsedInstr::from_str("bcl 0, 2, $+0x7e80").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("bc 0, 8, 0xFFFF9A44").unwrap(), &ParsedInstr::from_str("bc 0, 8, $-0x65c0").unwrap(), )); // xenia prints "tw" instead of "twi" even for trap-with-immediate assert!(compare_xenia_instr( &ParsedInstr::from_str("tw 0, r0, -0x1FC5").unwrap(), &ParsedInstr::from_str("twi 0, r0, -0x1fc5").unwrap(), )); // and xenia doesn't do the condition aliases.. assert!(compare_xenia_instr( &ParsedInstr::from_str("tw 24, r8, -0x1B5").unwrap(), &ParsedInstr::from_str("twnei r8, -0x1b5").unwrap(), )); // same for td/tdi assert!(compare_xenia_instr( &ParsedInstr::from_str("tdi 4, r8, -0x1B5").unwrap(), &ParsedInstr::from_str("tdeqi r8, -0x1b5").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("vpkd3d128").unwrap(), &ParsedInstr::from_str("vpkd3d128 vr64, vr47, 1, 0, 2").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("vspltb vr2, vr0, 0x10").unwrap(), &ParsedInstr::from_str("vspltb vr2, vr0, 0").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("bclr 3, 29").unwrap(), &ParsedInstr::from_str("bdzlr le, crf7").unwrap(), )); assert!(compare_xenia_instr( &ParsedInstr::from_str("mfcr r3").unwrap(), &ParsedInstr::from_str("mfocrf r3, 0x7e").unwrap(), )); } #[test] #[ignore] // unignored for some tests fn differential_xenia_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::Xenon) .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 xenia_texts = tools::xenia_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 xenia_text = &xenia_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 xenia failed to decode and we failed to decode, move on; we agree! if xenia_text.contains(".long ") { continue; } eprintln!("xenia says: {:?}", xenia_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) }, Ok(instr) => instr, }; let displayed = format!("{}", instr); let display_noalias = format!("{}", instr.display_rules(&no_alias)); let parsed_xenia = ParsedInstr::from_str(&xenia_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_xenia_instr(&parsed_xenia, &parsed_yax) { eprintln!("miss {:02x}{:02x}{:02x}{:02x}: {} vs {}", bytes[0], bytes[1], bytes[2], bytes[3], xenia_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_00; let total_end = 0xff_ff_ff_ff; //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)); }