use yaxpeax_arch::Decoder; use yaxpeax_power::{InstDecoder, IsaVersion}; use crate::{ParsedInstr, ParsedOperand}; use crate::tools; fn compare_gekko_instr(gekko: &ParsedInstr, yax: &ParsedInstr) -> bool { // generally instructions "just compare", but there's some stuff gekko does or doesn't do // (mostly to do with assembler aliases) that are handled here... if gekko == yax { return true; } if gekko.operands == yax.operands { // TODO: POWER vs Power ISA, this changed around PPC if gekko.mnemonic == "lm" && yax.mnemonic == "lmw" { return true; } } if gekko.mnemonic == yax.mnemonic && ["mfspr", "mtspr"].contains(&yax.mnemonic.as_str()) { // ppcd provides names for many special-purpose registers which yax does not, making // comparison .. challenging. as long as we agree on the mnemonic, other comparisons mean // we're probably agreeing on the numeric values at least. return true; } if gekko.mnemonic == "mftbl" { return yax.mnemonic == "mftb" && yax.operands[0] == gekko.operands[0] && yax.operands[1] == ParsedOperand::Immediate(0x10c); } if gekko.mnemonic == "sc" { if yax.mnemonic == "sc" { // .. good enough. gekko doesn't print service call numbers. 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; } let mut gekko_br = None; let mut yax_br = None; if gekko.mnemonic.ends_with("+") || gekko.mnemonic.ends_with("-") { gekko_br = Some(&gekko.mnemonic[..gekko.mnemonic.len() - 1]); } if yax.mnemonic.ends_with("+") || yax.mnemonic.ends_with("-") { yax_br = Some(&yax.mnemonic[..yax.mnemonic.len() - 1]); } if gekko_br == Some(&yax.mnemonic) || yax_br == Some(&gekko.mnemonic) || (yax_br == gekko_br && yax_br.is_some()) { // ppcd does a really.. odd job of branch hints in bcx(). it looks like it interprets the // +/- branch prediction hints as hints that a branch will go forward or backwards instead. // so you get hints when the BO field does not mean one, and reversed hints if the branch // target is relative and negative. so lets just test the non-hinted form and operands.. return yax.operands == gekko.operands; } if yax.mnemonic == gekko.mnemonic && yax.mnemonic == "dcbt" { if yax.operands[0..2] == gekko.operands[0..2] { // i suspect ppcd and dolphin generally ignores the third operand (aka TH, Touch Hint), // which would be harmless from an emulator point of view. objdump prints this operand, // but i'm not sure if it should or shouldn't be present in practice. does hardware // *actually* do anything different based on the bits in this location? return true; } } if yax.mnemonic == gekko.mnemonic && ["b", "bl", "ba", "bla"].contains(&yax.mnemonic.as_str()) { // ppc omits the control register for some instructions like b, bl, bla, etc. if yax.operands.len() == 2 && gekko.operands.len() == 1 && yax.operands[1] == gekko.operands[0] { return true; } } if yax.mnemonic == gekko.mnemonic && ["blr", "blrl", "bctr", "bctrl"].contains(&yax.mnemonic.as_str()) { // ppc omits the control register for some instructions like b, bl, bla, etc. if yax.operands.len() == 1 && gekko.operands.len() == 0 { if let ParsedOperand::CrField(_) = yax.operands[0] { 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_gekko_aliases() { assert!(compare_gekko_instr( &ParsedInstr::from_str("sc ").unwrap(), &ParsedInstr::from_str("sc 0x59").unwrap(), )); assert!(compare_gekko_instr( &ParsedInstr::from_str("bne+ cr1, 0xFFFFCDBC").unwrap(), &ParsedInstr::from_str("bne cr1, $-0x3248").unwrap(), )); assert!(compare_gekko_instr( &ParsedInstr::from_str("mfspr r0, 983").unwrap(), &ParsedInstr::from_str("mfspr r0, spr766").unwrap(), )); } #[test] #[ignore] // unignored for some tests fn differential_gekko_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::Gekko) .with_be(false); 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 gekko_texts = tools::gekko_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 gekko_text = &gekko_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 gekko failed to decode and we failed to decode, move on; we agree! if gekko_text.contains(".long ") { continue; } if gekko_text.starts_with("ps_cmp") { // ppcd allows the three bits above the opcode selector to be anything, // while objdump is more picky. yax is also picky here. continue; } eprintln!("gekko says: {:?}", gekko_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 parsed_gekko = ParsedInstr::from_str(&gekko_text).expect("can parse objdump"); let parsed_yax = ParsedInstr::from_str(&displayed).expect("can parse decoder output"); if !compare_gekko_instr(&parsed_gekko, &parsed_yax) { eprintln!("miss {:02x}{:02x}{:02x}{:02x}: {} vs {}", bytes[0], bytes[1], bytes[2], bytes[3], gekko_text, displayed); compare_ctx.missed.fetch_add(1, Ordering::Relaxed); } } } } 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)); }