use super::decode::{resolve_modrm32, Operand};
use super::isa_int::{Cpu, StepOk};
use super::mmu::Mmu;
use super::Trap;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum SsePrefix {
Np,
P66,
Pf3,
Pf2,
}
impl SsePrefix {
fn current(cpu: &Cpu) -> Self {
match (cpu.op_size_16(), cpu.rep_prefix_byte()) {
(true, _) => SsePrefix::P66,
(false, Some(0xF3)) => SsePrefix::Pf3,
(false, Some(0xF2)) => SsePrefix::Pf2,
_ => SsePrefix::Np,
}
}
}
fn opcode_id(op2: u8, pfx: SsePrefix) -> u32 {
let pfx_bits = match pfx {
SsePrefix::Np => 0x0000,
SsePrefix::P66 => 0x6600,
SsePrefix::Pf3 => 0xF300,
SsePrefix::Pf2 => 0xF200,
};
pfx_bits | 0x0F00 | u32::from(op2)
}
pub fn dispatch(cpu: &mut Cpu, mmu: &mut Mmu, op2: u8, entry_eip: u32) -> Result<StepOk, Trap> {
cpu.sse_dispatch_count = cpu.sse_dispatch_count.wrapping_add(1);
let pfx = SsePrefix::current(cpu);
match (op2, pfx) {
(0x12, SsePrefix::Np) => movlps_or_movhlps(cpu, mmu),
(0x13, SsePrefix::Np) => movlps_store(cpu, mmu),
(0x17, SsePrefix::Np) => movhps_store(cpu, mmu),
(0x16, SsePrefix::Np) => movhps_or_movlhps(cpu, mmu),
_ => Err(Trap::UndefinedOpcode {
eip: entry_eip,
opcode: opcode_id(op2, pfx),
}),
}
}
fn movlps_or_movhlps(cpu: &mut Cpu, mmu: &mut Mmu) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let dst_idx = (mr.reg & 0x7) as usize;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.advance_eip(consumed as u32);
let new_low: u64 = match op {
Operand::Reg32(_) => {
let src = cpu.xmm[(mr.rm & 0x7) as usize];
(src >> 64) as u64
}
Operand::Mem32(addr) => {
mmu.load64(cpu.seg_translate(addr))?
}
};
let prev = cpu.xmm[dst_idx];
let high = (prev >> 64) as u64;
cpu.xmm[dst_idx] = pack_lh(new_low, high);
Ok(StepOk::Continued)
}
fn movlps_store(cpu: &mut Cpu, mmu: &mut Mmu) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.advance_eip(consumed as u32);
let addr = match op {
Operand::Mem32(a) => cpu.seg_translate(a),
Operand::Reg32(_) => {
return Err(Trap::UndefinedOpcode {
eip: cpu.regs.eip.wrapping_sub(consumed as u32 + 2),
opcode: 0x0F13,
});
}
};
let low = cpu.xmm[(mr.reg & 0x7) as usize] as u64;
mmu.write(addr, &low.to_le_bytes())?;
Ok(StepOk::Continued)
}
fn movhps_store(cpu: &mut Cpu, mmu: &mut Mmu) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.advance_eip(consumed as u32);
let addr = match op {
Operand::Mem32(a) => cpu.seg_translate(a),
Operand::Reg32(_) => {
return Err(Trap::UndefinedOpcode {
eip: cpu.regs.eip.wrapping_sub(consumed as u32 + 2),
opcode: 0x0F17,
});
}
};
let high = (cpu.xmm[(mr.reg & 0x7) as usize] >> 64) as u64;
mmu.write(addr, &high.to_le_bytes())?;
Ok(StepOk::Continued)
}
fn movhps_or_movlhps(cpu: &mut Cpu, mmu: &mut Mmu) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let dst_idx = (mr.reg & 0x7) as usize;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.advance_eip(consumed as u32);
let new_high: u64 = match op {
Operand::Reg32(_) => {
cpu.xmm[(mr.rm & 0x7) as usize] as u64
}
Operand::Mem32(addr) => mmu.load64(cpu.seg_translate(addr))?,
};
let prev = cpu.xmm[dst_idx];
let low = prev as u64;
cpu.xmm[dst_idx] = pack_lh(low, new_high);
Ok(StepOk::Continued)
}
pub fn dispatch_xmm_int(
cpu: &mut Cpu,
mmu: &mut Mmu,
op2: u8,
entry_eip: u32,
) -> Result<StepOk, Trap> {
cpu.sse_dispatch_count = cpu.sse_dispatch_count.wrapping_add(1);
match op2 {
0x6F => movdqa_load_xmm(cpu, mmu),
0x7F => movdqa_store_xmm(cpu, mmu),
0xEF => pxor_xmm(cpu, mmu),
_ => Err(Trap::UndefinedOpcode {
eip: entry_eip,
opcode: 0x0166_0000 | u32::from(op2),
}),
}
}
fn movdqa_load_xmm(cpu: &mut Cpu, mmu: &mut Mmu) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.advance_eip(consumed as u32);
let dst_idx = (mr.reg & 0x7) as usize;
let value: u128 = match op {
Operand::Reg32(_) => cpu.xmm[(mr.rm & 0x7) as usize],
Operand::Mem32(addr) => {
let addr = cpu.seg_translate(addr);
let low = mmu.load64(addr)?;
let high = mmu.load64(addr.wrapping_add(8))?;
pack_lh(low, high)
}
};
cpu.xmm[dst_idx] = value;
Ok(StepOk::Continued)
}
fn movdqa_store_xmm(cpu: &mut Cpu, mmu: &mut Mmu) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.advance_eip(consumed as u32);
let value = cpu.xmm[(mr.reg & 0x7) as usize];
let low = value as u64;
let high = (value >> 64) as u64;
match op {
Operand::Reg32(_) => cpu.xmm[(mr.rm & 0x7) as usize] = value,
Operand::Mem32(addr) => {
let addr = cpu.seg_translate(addr);
mmu.write(addr, &low.to_le_bytes())?;
mmu.write(addr.wrapping_add(8), &high.to_le_bytes())?;
}
}
Ok(StepOk::Continued)
}
fn pxor_xmm(cpu: &mut Cpu, mmu: &mut Mmu) -> Result<StepOk, Trap> {
let mr = cpu.fetch_modrm(mmu)?;
let bytes = cpu.peek_after_modrm(mmu, 16)?;
let (op, consumed) = resolve_modrm32(mr, &bytes, &cpu.regs)?;
cpu.advance_eip(consumed as u32);
let dst_idx = (mr.reg & 0x7) as usize;
let src: u128 = match op {
Operand::Reg32(_) => cpu.xmm[(mr.rm & 0x7) as usize],
Operand::Mem32(addr) => {
let addr = cpu.seg_translate(addr);
let low = mmu.load64(addr)?;
let high = mmu.load64(addr.wrapping_add(8))?;
pack_lh(low, high)
}
};
cpu.xmm[dst_idx] ^= src;
Ok(StepOk::Continued)
}
#[inline]
fn pack_lh(low: u64, high: u64) -> u128 {
(u128::from(high) << 64) | u128::from(low)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::emulator::mmu::Perm;
use crate::emulator::regs::Reg32;
fn make_cpu() -> (Cpu, Mmu) {
let mut mmu = Mmu::new();
mmu.map(0x1000, 0x1000, Perm::R | Perm::W | Perm::X);
let cpu = Cpu::new();
(cpu, mmu)
}
#[test]
fn movhlps_copies_high_half_into_low_half() {
let (mut cpu, mut mmu) = make_cpu();
cpu.regs.eip = 0x1000;
cpu.xmm[0] = pack_lh(0xAAAA_AAAA_AAAA_AAAA, 0xDEAD_BEEF_DEAD_BEEF);
cpu.xmm[1] = pack_lh(0x1111_1111_1111_1111, 0xCAFE_F00D_CAFE_F00D);
mmu.write_initializer(0x1000, &[0x0F, 0x12, 0xC1]).unwrap();
let _ = cpu.step(&mut mmu).unwrap();
assert_eq!(cpu.xmm[0] as u64, 0xCAFE_F00D_CAFE_F00D);
assert_eq!((cpu.xmm[0] >> 64) as u64, 0xDEAD_BEEF_DEAD_BEEF);
assert_eq!(cpu.sse_dispatch_count, 1);
}
#[test]
fn movlps_loads_low_half_from_memory() {
let (mut cpu, mut mmu) = make_cpu();
mmu.map(0x4000, 0x1000, Perm::R | Perm::W);
let payload = 0x0123_4567_89AB_CDEF_u64;
mmu.write_initializer(0x4000, &payload.to_le_bytes())
.unwrap();
cpu.regs.eip = 0x1000;
cpu.regs.set32(Reg32::Esi, 0x4000);
cpu.xmm[0] = pack_lh(0xAAAA_AAAA_AAAA_AAAA, 0xBBBB_BBBB_BBBB_BBBB);
mmu.write_initializer(0x1000, &[0x0F, 0x12, 0x06]).unwrap();
let _ = cpu.step(&mut mmu).unwrap();
assert_eq!(cpu.xmm[0] as u64, payload);
assert_eq!((cpu.xmm[0] >> 64) as u64, 0xBBBB_BBBB_BBBB_BBBB);
}
}