use crate::arch::x86::{DecodedInsn, Mnemonic, OpKind, format_intel};
use crate::ast::Stmt;
use super::{Candidate, Pattern, PatternCtx};
pub struct MemViaReg;
impl Pattern for MemViaReg {
fn name(&self) -> &'static str {
"mem_via_reg"
}
fn tentative(
&self,
ctx: &PatternCtx,
insns: &[DecodedInsn],
start: usize,
) -> Option<Candidate> {
let load = insns.get(start)?;
let store = insns.get(start + 1)?;
if load.iced.mnemonic() != Mnemonic::Mov || store.iced.mnemonic() != Mnemonic::Mov {
return None;
}
if load.iced.op0_kind() != OpKind::Register {
return None;
}
let scratch = load.iced.op0_register();
if store.iced.op0_kind() != OpKind::Memory {
return None;
}
if store.iced.op1_kind() != OpKind::Register || store.iced.op1_register() != scratch {
return None;
}
let (_, src) = super::mov::split_two_operands(&format_intel(&load.iced), "mov ")?;
let (dst, _) = super::mov::split_two_operands(&format_intel(&store.iced), "mov ")?;
let sp_load = ctx.sp_delta_at.get(&load.iced.ip()).copied();
let sp_store = ctx.sp_delta_at.get(&store.iced.ip()).copied();
let dst = crate::arch::x86::rename_operand_in_ctx(&dst, sp_store);
let src = crate::arch::x86::rename_operand_in_ctx(&src, sp_load);
let mut bytes = Vec::with_capacity(load.original_bytes.len() + store.original_bytes.len());
bytes.extend_from_slice(&load.original_bytes);
bytes.extend_from_slice(&store.original_bytes);
Some(Candidate {
pattern: self.name(),
start,
consumed: 2,
priority: 60,
stmts: vec![Stmt::Move { dst, src, bytes }],
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::arch::x86::{Bitness, decode};
use std::collections::HashMap;
fn ctx() -> PatternCtx<'static> {
let map: &'static HashMap<u64, String> = Box::leak(Box::new(HashMap::new()));
let sp_map: &'static HashMap<u64, i64> = Box::leak(Box::new(HashMap::new()));
PatternCtx {
fn_addr_start: 0,
fn_addr_end: u64::MAX,
name_at: map,
sp_delta_at: sp_map,
}
}
#[test]
fn folds_load_store_pair() {
let bytes = [0x8b, 0x45, 0x08, 0x89, 0x45, 0xfc];
let insns = decode(Bitness::Bits32, &bytes, 0).unwrap();
let c = MemViaReg.tentative(&ctx(), &insns, 0).expect("match");
assert_eq!(c.consumed, 2);
let Some(Stmt::Move {
dst,
src,
bytes: out,
}) = c.stmts.first()
else {
panic!("expected Stmt::Move");
};
assert_eq!(dst, "var_4");
assert_eq!(src, "arg_8");
assert_eq!(out, &bytes);
}
#[test]
fn skips_reg_to_reg_store() {
let bytes = [0x8b, 0x45, 0x08, 0x89, 0xc3];
let insns = decode(Bitness::Bits32, &bytes, 0).unwrap();
assert!(MemViaReg.tentative(&ctx(), &insns, 0).is_none());
}
#[test]
fn skips_when_scratch_register_differs() {
let bytes = [0x8b, 0x45, 0x08, 0x89, 0x5d, 0xfc];
let insns = decode(Bitness::Bits32, &bytes, 0).unwrap();
assert!(MemViaReg.tentative(&ctx(), &insns, 0).is_none());
}
}