use std::collections::HashMap;
use std::fmt;
use super::x86_vex_encoding::{
vex_invert_reg, vex_recover_reg, VexMandatoryPrefix, VexVectorLength, VEX_PP_NONE,
VEX_REG_HIGH_BIT, VEX_REG_MASK,
};
pub const XOP_PREFIX: u8 = 0x8F;
pub const XOP_MAP8: u8 = 0x08;
pub const XOP_MAP9: u8 = 0x09;
pub const XOP_MAPA: u8 = 0x0A;
const XOP_BYTE1_R_MASK: u8 = 0x80;
const XOP_BYTE1_X_MASK: u8 = 0x40;
const XOP_BYTE1_B_MASK: u8 = 0x20;
const XOP_BYTE1_MMMMM_MASK: u8 = 0x1F;
const XOP_BYTE2_W_MASK: u8 = 0x80;
const XOP_BYTE2_VVVV_MASK: u8 = 0x78;
const XOP_BYTE2_L_MASK: u8 = 0x04;
const XOP_BYTE2_PP_MASK: u8 = 0x03;
pub const XOP_MAX_REG: u8 = 15;
pub const TDNOW_ESCAPE_0F: u8 = 0x0F;
pub const TDNOW_ESCAPE_SECOND: u8 = 0x0F;
pub mod tdnow_opcodes {
pub const PI2FD: u8 = 0x0D;
pub const PI2FW: u8 = 0x0C;
pub const PF2ID: u8 = 0x1D;
pub const PF2IW: u8 = 0x1C;
pub const PFACC: u8 = 0xAE;
pub const PFADD: u8 = 0x9E;
pub const PFCMPEQ: u8 = 0xB0;
pub const PFCMPGE: u8 = 0x90;
pub const PFCMPGT: u8 = 0xA0;
pub const PFMAX: u8 = 0xA4;
pub const PFMIN: u8 = 0x94;
pub const PFMUL: u8 = 0xB4;
pub const PFNACC: u8 = 0x8A;
pub const PFPNACC: u8 = 0x8E;
pub const PFRCP: u8 = 0x96;
pub const PFRCPIT1: u8 = 0xA6;
pub const PFRCPIT2: u8 = 0xB6;
pub const PFRSQIT1: u8 = 0xA7;
pub const PFRSQRT: u8 = 0x97;
pub const PFSUB: u8 = 0x9A;
pub const PFSUBR: u8 = 0xAA;
pub const PMULHRW: u8 = 0xB7;
pub const PAVGUSB: u8 = 0xBF;
}
pub const FEMMS_OPCODE: [u8; 2] = [0x0F, 0x0E];
pub const PREFETCH_OPCODE_PREFIX: [u8; 2] = [0x0F, 0x0D];
pub const PREFETCHW_REG_FIELD: u8 = 0x01;
pub const TDNOW_MMX_REG_COUNT: u8 = 8;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum XopOpcodeMap {
Map8 = 0x08,
Map9 = 0x09,
MapA = 0x0A,
}
impl XopOpcodeMap {
pub const fn mmmmm(&self) -> u8 {
*self as u8
}
pub const fn from_mmmmm(mmmmm: u8) -> Option<Self> {
match mmmmm {
0x08 => Some(Self::Map8),
0x09 => Some(Self::Map9),
0x0A => Some(Self::MapA),
_ => None,
}
}
pub const fn name(&self) -> &'static str {
match self {
Self::Map8 => "XOP8",
Self::Map9 => "XOP9",
Self::MapA => "XOPA",
}
}
}
impl fmt::Display for XopOpcodeMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct XopPrefixConfig {
pub r: bool,
pub x: bool,
pub b: bool,
pub mmmmm: u8,
pub w: bool,
pub vvvv: u8,
pub l: bool,
pub pp: u8,
}
impl Default for XopPrefixConfig {
fn default() -> Self {
Self {
r: false,
x: false,
b: false,
mmmmm: XOP_MAP8,
w: false,
vvvv: 0,
l: false,
pp: VEX_PP_NONE,
}
}
}
impl XopPrefixConfig {
pub const fn new() -> Self {
Self {
r: false,
x: false,
b: false,
mmmmm: XOP_MAP8,
w: false,
vvvv: 0,
l: false,
pp: VEX_PP_NONE,
}
}
pub fn with_map(mut self, map: XopOpcodeMap) -> Self {
self.mmmmm = map.mmmmm();
self
}
pub fn with_prefix(mut self, pfx: VexMandatoryPrefix) -> Self {
self.pp = pfx.pp_bits();
self
}
pub fn with_vector_length(mut self, vl: VexVectorLength) -> Self {
self.l = vl.l_bit();
self
}
pub fn with_vvvv(mut self, reg: u8) -> Self {
self.vvvv = vex_invert_reg(reg & VEX_REG_MASK);
self
}
pub fn with_dest_reg(mut self, reg: u8) -> Self {
self.r = (reg & VEX_REG_HIGH_BIT) != 0;
self
}
pub fn with_base_reg(mut self, reg: u8) -> Self {
self.b = (reg & VEX_REG_HIGH_BIT) != 0;
self
}
pub fn with_index_reg(mut self, reg: u8) -> Self {
self.x = (reg & VEX_REG_HIGH_BIT) != 0;
self
}
pub fn with_w(mut self, w: bool) -> Self {
self.w = w;
self
}
pub fn from_registers(
map: XopOpcodeMap,
prefix: VexMandatoryPrefix,
vl: VexVectorLength,
vvvv_reg: u8,
dest_reg: u8,
src_or_base_reg: u8,
sib_index: Option<u8>,
w: bool,
) -> Self {
let mut cfg = Self::new()
.with_map(map)
.with_prefix(prefix)
.with_vector_length(vl)
.with_vvvv(vvvv_reg)
.with_dest_reg(dest_reg)
.with_base_reg(src_or_base_reg)
.with_w(w);
if let Some(idx) = sib_index {
cfg = cfg.with_index_reg(idx);
}
cfg
}
pub const fn prefix_byte_count(&self) -> u8 {
3
}
}
pub struct XopPrefixBuilder;
impl XopPrefixBuilder {
#[inline]
pub fn build(
r: bool,
x: bool,
b: bool,
mmmmm: u8,
w: bool,
vvvv: u8,
l: bool,
pp: u8,
) -> [u8; 3] {
let byte1: u8 = ((!r as u8) << 7)
| ((!x as u8) << 6)
| ((!b as u8) << 5)
| (mmmmm & XOP_BYTE1_MMMMM_MASK);
let byte2: u8 = ((w as u8) << 7)
| ((vvvv & VEX_REG_MASK) << 3)
| ((l as u8) << 2)
| (pp & XOP_BYTE2_PP_MASK);
[XOP_PREFIX, byte1, byte2]
}
#[inline]
pub fn build_from_config(config: &XopPrefixConfig) -> [u8; 3] {
Self::build(
config.r,
config.x,
config.b,
config.mmmmm,
config.w,
config.vvvv,
config.l,
config.pp,
)
}
pub fn build_vec(config: &XopPrefixConfig) -> Vec<u8> {
Self::build_from_config(config).to_vec()
}
pub fn build_from_regs(
map: XopOpcodeMap,
prefix: VexMandatoryPrefix,
vl: VexVectorLength,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
w: bool,
) -> [u8; 3] {
let cfg =
XopPrefixConfig::from_registers(map, prefix, vl, src1_reg, dest_reg, src2_reg, None, w);
Self::build_from_config(&cfg)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct XopDecodedPrefix {
pub r: bool,
pub x: bool,
pub b: bool,
pub mmmmm: u8,
pub w: bool,
pub vvvv: u8,
pub l: bool,
pub pp: u8,
}
impl XopDecodedPrefix {
pub fn decode(byte1: u8, byte2: u8) -> Self {
Self {
r: (byte1 & XOP_BYTE1_R_MASK) == 0,
x: (byte1 & XOP_BYTE1_X_MASK) == 0,
b: (byte1 & XOP_BYTE1_B_MASK) == 0,
mmmmm: byte1 & XOP_BYTE1_MMMMM_MASK,
w: (byte2 & XOP_BYTE2_W_MASK) != 0,
vvvv: (byte2 >> 3) & VEX_REG_MASK,
l: (byte2 & XOP_BYTE2_L_MASK) != 0,
pp: byte2 & XOP_BYTE2_PP_MASK,
}
}
pub fn decode_from_slice(bytes: &[u8]) -> Option<(Self, usize)> {
if bytes.len() < 3 || bytes[0] != XOP_PREFIX {
return None;
}
Some((Self::decode(bytes[1], bytes[2]), 3))
}
pub fn effective_reg_r(&self, reg: u8) -> u8 {
if self.r {
reg & 0x07
} else {
reg | VEX_REG_HIGH_BIT
}
}
pub fn effective_reg_x(&self, reg: u8) -> u8 {
if self.x {
reg & 0x07
} else {
reg | VEX_REG_HIGH_BIT
}
}
pub fn effective_reg_b(&self, reg: u8) -> u8 {
if self.b {
reg & 0x07
} else {
reg | VEX_REG_HIGH_BIT
}
}
pub fn true_vvvv(&self) -> u8 {
vex_recover_reg(self.vvvv)
}
pub fn opcode_map(&self) -> Option<XopOpcodeMap> {
XopOpcodeMap::from_mmmmm(self.mmmmm)
}
pub fn mandatory_prefix(&self) -> VexMandatoryPrefix {
VexMandatoryPrefix::from_pp(self.pp).unwrap_or(VexMandatoryPrefix::None)
}
pub fn vector_length(&self) -> VexVectorLength {
VexVectorLength::from_l_bit(self.l)
}
pub fn to_config(&self) -> XopPrefixConfig {
XopPrefixConfig {
r: self.r,
x: self.x,
b: self.b,
mmmmm: self.mmmmm,
w: self.w,
vvvv: self.vvvv,
l: self.l,
pp: self.pp,
}
}
}
impl fmt::Display for XopDecodedPrefix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"XOP R={} X={} B={} mmmmm={:05b} W={} vvvv={:04b} L={} pp={:02b}",
self.r as u8,
self.x as u8,
self.b as u8,
self.mmmmm,
self.w as u8,
self.vvvv,
self.l as u8,
self.pp,
)
}
}
#[derive(Debug, Clone)]
pub struct XopInstructionCatalog;
impl XopInstructionCatalog {
pub fn map8_instructions() -> Vec<(&'static str, u8, &'static str)> {
vec![
(
"vphaddbd",
0xC2,
"Packed Horizontal Add Bytes to Doubleword",
),
("vphaddbq", 0xC3, "Packed Horizontal Add Bytes to Quadword"),
(
"vphaddwd",
0xC6,
"Packed Horizontal Add Words to Doubleword",
),
("vphaddwq", 0xC7, "Packed Horizontal Add Words to Quadword"),
(
"vphadddq",
0xCB,
"Packed Horizontal Add Doubleword to Quadword",
),
(
"vphaddubd",
0xD2,
"Packed Horizontal Add Unsigned Bytes to Doubleword",
),
(
"vphaddubq",
0xD3,
"Packed Horizontal Add Unsigned Bytes to Quadword",
),
(
"vphadduwd",
0xD6,
"Packed Horizontal Add Unsigned Words to Doubleword",
),
(
"vphadduwq",
0xD7,
"Packed Horizontal Add Unsigned Words to Quadword",
),
(
"vphaddudq",
0xDB,
"Packed Horizontal Add Unsigned Doubleword to Quadword",
),
(
"vphsubbw",
0xE2,
"Packed Horizontal Subtract Bytes to Words",
),
(
"vphsubdq",
0xE3,
"Packed Horizontal Subtract Doubleword to Quadword",
),
(
"vphsubwd",
0xE6,
"Packed Horizontal Subtract Words to Doubleword",
),
(
"vpmacssww",
0x82,
"Packed Multiply-Accumulate Signed Signed Words to Words",
),
(
"vpmacsswd",
0x86,
"Packed Multiply-Accumulate Signed Signed Words to Doubleword",
),
(
"vpmacssdql",
0x9A,
"Packed Multiply-Accumulate Signed Signed Doubleword to Quadword Low",
),
(
"vpmacssdqh",
0x9E,
"Packed Multiply-Accumulate Signed Signed Doubleword to Quadword High",
),
(
"vpmacssdd",
0x8A,
"Packed Multiply-Accumulate Signed Signed Doubleword to Doubleword",
),
(
"vpmacsww",
0x92,
"Packed Multiply-Accumulate Signed Words to Words",
),
(
"vpmacswd",
0x96,
"Packed Multiply-Accumulate Signed Words to Doubleword",
),
(
"vpmacsdql",
0xB2,
"Packed Multiply-Accumulate Signed Doubleword to Quadword Low",
),
(
"vpmacsdqh",
0xB6,
"Packed Multiply-Accumulate Signed Doubleword to Quadword High",
),
(
"vpmacsdd",
0x9E,
"Packed Multiply-Accumulate Signed Doubleword to Doubleword",
),
(
"vpmadcsswd",
0xA2,
"Packed Multiply-Add and Accumulate Signed Signed Words to Doubleword",
),
(
"vpmadcswd",
0xB2,
"Packed Multiply-Add and Accumulate Signed Words to Doubleword",
),
]
}
pub fn map8_shift_instructions() -> Vec<(&'static str, u8, &'static str)> {
vec![
("vprotb", 0x90, "Packed Rotate Bytes"),
("vprotw", 0x91, "Packed Rotate Words"),
("vprotd", 0x92, "Packed Rotate Doublewords"),
("vprotq", 0x93, "Packed Rotate Quadwords"),
("vpshab", 0x98, "Packed Shift Arithmetic Bytes"),
("vpshaw", 0x99, "Packed Shift Arithmetic Words"),
("vpshad", 0x9A, "Packed Shift Arithmetic Doublewords"),
("vpshaq", 0x9B, "Packed Shift Arithmetic Quadwords"),
("vpshlb", 0x94, "Packed Shift Logical Bytes"),
("vpshlw", 0x95, "Packed Shift Logical Words"),
("vpshld", 0x96, "Packed Shift Logical Doublewords"),
("vpshlq", 0x97, "Packed Shift Logical Quadwords"),
]
}
pub fn map9_instructions() -> Vec<(&'static str, u8, &'static str)> {
vec![
("vfrczps", 0x80, "Fraction Extraction Packed Single"),
("vfrczpd", 0x81, "Fraction Extraction Packed Double"),
("vfrczss", 0x82, "Fraction Extraction Scalar Single"),
("vfrczsd", 0x83, "Fraction Extraction Scalar Double"),
("vpcmov", 0xA0, "Packed Conditional Move"),
("vpperm", 0xA1, "Packed Permute Bytes"),
("vpcomb", 0x8C, "Packed Compare Bytes"),
("vpcomw", 0x8D, "Packed Compare Words"),
("vpcomd", 0x8E, "Packed Compare Doublewords"),
("vpcomq", 0x8F, "Packed Compare Quadwords"),
("vpcomub", 0xCC, "Packed Compare Unsigned Bytes"),
("vpcomuw", 0xCD, "Packed Compare Unsigned Words"),
("vpcomud", 0xCE, "Packed Compare Unsigned Doublewords"),
("vpcomuq", 0xCF, "Packed Compare Unsigned Quadwords"),
]
}
pub fn lookup(map: u8, opcode: u8) -> Option<&'static str> {
match map {
8 => {
for (name, op, _) in Self::map8_instructions()
.iter()
.chain(Self::map8_shift_instructions().iter())
{
if *op == opcode {
return Some(name);
}
}
None
}
9 => {
for (name, op, _) in Self::map9_instructions() {
if op == opcode as u8 {
return Some(name);
}
}
None
}
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct XopEncodedInstruction {
pub xop_prefix: Vec<u8>,
pub opcode: Vec<u8>,
pub modrm: Option<u8>,
pub sib: Option<u8>,
pub displacement: Vec<u8>,
pub immediate: Vec<u8>,
pub map: XopOpcodeMap,
pub mnemonic: Option<&'static str>,
}
impl XopEncodedInstruction {
pub fn new(map: XopOpcodeMap) -> Self {
Self {
xop_prefix: Vec::new(),
opcode: Vec::new(),
modrm: None,
sib: None,
displacement: Vec::new(),
immediate: Vec::new(),
map,
mnemonic: None,
}
}
pub fn assemble(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(self.total_length() as usize);
bytes.extend_from_slice(&self.xop_prefix);
bytes.extend_from_slice(&self.opcode);
if let Some(modrm) = self.modrm {
bytes.push(modrm);
}
if let Some(sib) = self.sib {
bytes.push(sib);
}
bytes.extend_from_slice(&self.displacement);
bytes.extend_from_slice(&self.immediate);
bytes
}
pub fn total_length(&self) -> u16 {
let mut len: u16 = self.xop_prefix.len() as u16;
len += self.opcode.len() as u16;
if self.modrm.is_some() {
len += 1;
}
if self.sib.is_some() {
len += 1;
}
len += self.displacement.len() as u16;
len += self.immediate.len() as u16;
len
}
pub fn is_valid(&self) -> bool {
self.total_length() <= 15
}
}
impl fmt::Display for XopEncodedInstruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bytes = self.assemble();
write!(f, "XOP[{}] ", bytes.len())?;
for b in &bytes {
write!(f, "{:02X} ", b)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TdNowInstruction {
pub imm8: u8,
pub mnemonic: &'static str,
pub description: &'static str,
pub uses_pairing: bool,
}
pub struct TdNowCatalog;
impl TdNowCatalog {
pub fn all_instructions() -> Vec<TdNowInstruction> {
vec![
TdNowInstruction {
imm8: tdnow_opcodes::PI2FD,
mnemonic: "pi2fd",
description: "Packed 32-bit Integer to Float",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PI2FW,
mnemonic: "pi2fw",
description: "Packed 16-bit Integer to Float",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PF2ID,
mnemonic: "pf2id",
description: "Packed Float to 32-bit Integer",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PF2IW,
mnemonic: "pf2iw",
description: "Packed Float to 16-bit Integer",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFACC,
mnemonic: "pfacc",
description: "Packed Float Accumulate",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFADD,
mnemonic: "pfadd",
description: "Packed Float Add",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFCMPEQ,
mnemonic: "pfcmpeq",
description: "Packed Float Compare Equal",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFCMPGE,
mnemonic: "pfcmpge",
description: "Packed Float Compare >= ",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFCMPGT,
mnemonic: "pfcmpgt",
description: "Packed Float Compare >",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFMAX,
mnemonic: "pfmax",
description: "Packed Float Maximum",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFMIN,
mnemonic: "pfmin",
description: "Packed Float Minimum",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFMUL,
mnemonic: "pfmul",
description: "Packed Float Multiply",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFNACC,
mnemonic: "pfnacc",
description: "Packed Float Negative Accumulate",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFPNACC,
mnemonic: "pfpnacc",
description: "Packed Float Pos-Neg Accumulate",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFRCP,
mnemonic: "pfrcp",
description: "Packed Float Reciprocal Approx",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFRCPIT1,
mnemonic: "pfrcpit1",
description: "Packed Float Reciprocal Iter1",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFRCPIT2,
mnemonic: "pfrcpit2",
description: "Packed Float Reciprocal Iter2",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFRSQIT1,
mnemonic: "pfrsqit1",
description: "Packed Float RSQRT Iter1",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFRSQRT,
mnemonic: "pfrsqrt",
description: "Packed Float RSQRT Approx",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFSUB,
mnemonic: "pfsub",
description: "Packed Float Subtract",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PFSUBR,
mnemonic: "pfsubr",
description: "Packed Float Reverse Subtract",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PMULHRW,
mnemonic: "pmulhrw",
description: "Packed Multiply High Round Word",
uses_pairing: false,
},
TdNowInstruction {
imm8: tdnow_opcodes::PAVGUSB,
mnemonic: "pavgusb",
description: "Packed Average Unsigned Byte",
uses_pairing: false,
},
]
}
pub fn lookup(imm8: u8) -> Option<TdNowInstruction> {
Self::all_instructions()
.into_iter()
.find(|inst| inst.imm8 == imm8)
}
pub fn is_valid_tdnow_opcode(imm8: u8) -> bool {
Self::lookup(imm8).is_some()
}
}
#[derive(Debug, Clone)]
pub struct TdNowEncodedInstruction {
pub bytes: Vec<u8>,
pub modrm: Option<u8>,
pub imm8: u8,
pub displacement: Vec<u8>,
pub mnemonic: Option<&'static str>,
}
impl TdNowEncodedInstruction {
pub fn new(imm8: u8) -> Self {
Self {
bytes: Vec::new(),
modrm: None,
imm8,
displacement: Vec::new(),
mnemonic: TdNowCatalog::lookup(imm8).map(|i| i.mnemonic),
}
}
pub fn assemble(&self) -> Vec<u8> {
let mut bytes = vec![TDNOW_ESCAPE_0F, TDNOW_ESCAPE_SECOND];
if let Some(modrm) = self.modrm {
bytes.push(modrm);
}
bytes.push(self.imm8);
bytes.extend_from_slice(&self.displacement);
bytes
}
pub fn total_length(&self) -> u16 {
let mut len: u16 = 2; if self.modrm.is_some() {
len += 1;
}
len += 1; len += self.displacement.len() as u16;
len
}
pub fn is_valid(&self) -> bool {
self.total_length() <= 15
}
pub fn mnemonic_str(&self) -> &str {
self.mnemonic.unwrap_or("???")
}
}
impl fmt::Display for TdNowEncodedInstruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bytes = self.assemble();
write!(f, "3DNow! ")?;
for b in &bytes {
write!(f, "{:02X} ", b)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct FemmsEncoder;
impl FemmsEncoder {
pub const OPCODE: [u8; 2] = FEMMS_OPCODE;
pub fn encode() -> Vec<u8> {
Self::OPCODE.to_vec()
}
pub const LENGTH: u8 = 2;
pub fn is_femms(bytes: &[u8]) -> bool {
bytes.len() >= 2 && bytes[0] == 0x0F && bytes[1] == 0x0E
}
}
#[derive(Debug, Clone)]
pub struct PrefetchEncoder;
impl PrefetchEncoder {
pub const PREFIX: [u8; 2] = PREFETCH_OPCODE_PREFIX;
pub const PREFETCH_VARIANTS: [(&str, u8); 8] = [
("prefetch", 0), ("prefetchw", 1), ("prefetcht0", 2), ("prefetcht1", 3), ("prefetcht2", 4), ("prefetchnta", 5), ("prefetch", 6), ("prefetch", 7), ];
pub fn encode(reg_field: u8, modrm_byte: u8, sib: Option<u8>, disp: &[u8]) -> Vec<u8> {
let mut bytes = vec![0x0F, 0x0D];
let modrm = (modrm_byte & 0xC7) | ((reg_field & 0x07) << 3);
bytes.push(modrm);
if let Some(s) = sib {
bytes.push(s);
}
bytes.extend_from_slice(disp);
bytes
}
pub fn encode_prefetcht0(modrm_byte: u8, sib: Option<u8>, disp: &[u8]) -> Vec<u8> {
Self::encode(2, modrm_byte, sib, disp)
}
pub fn encode_prefetcht1(modrm_byte: u8, sib: Option<u8>, disp: &[u8]) -> Vec<u8> {
Self::encode(3, modrm_byte, sib, disp)
}
pub fn encode_prefetcht2(modrm_byte: u8, sib: Option<u8>, disp: &[u8]) -> Vec<u8> {
Self::encode(4, modrm_byte, sib, disp)
}
pub fn encode_prefetchnta(modrm_byte: u8, sib: Option<u8>, disp: &[u8]) -> Vec<u8> {
Self::encode(5, modrm_byte, sib, disp)
}
pub fn encode_prefetchw(modrm_byte: u8, sib: Option<u8>, disp: &[u8]) -> Vec<u8> {
Self::encode(1, modrm_byte, sib, disp)
}
pub fn variant_name(reg_field: u8) -> &'static str {
Self::PREFETCH_VARIANTS
.iter()
.find(|&&(_, r)| r == (reg_field & 0x07))
.map(|&(name, _)| name)
.unwrap_or("prefetch")
}
}
#[derive(Debug, Clone)]
pub struct TdNowRegisterPairing;
impl TdNowRegisterPairing {
pub const MAX_MMX_REG: u8 = 7;
pub const MMX_REG_COUNT: u8 = 8;
pub fn is_valid_mmx_reg(reg: u8) -> bool {
reg <= Self::MAX_MMX_REG
}
pub fn form_pair(low_reg: u8) -> Option<(u8, u8)> {
if low_reg < Self::MAX_MMX_REG {
Some((low_reg, low_reg + 1))
} else {
None
}
}
pub fn is_valid_pair(low: u8, high: u8) -> bool {
high == low + 1 && low < Self::MAX_MMX_REG
}
pub fn can_form_pair(reg: u8) -> bool {
reg < Self::MAX_MMX_REG
}
pub fn all_pairs() -> Vec<(u8, u8)> {
(0..7).map(|r| (r, r + 1)).collect()
}
pub fn pair_name(low: u8, high: u8) -> String {
format!("mm{}/mm{}", low, high)
}
pub fn instruction_uses_pairing(_imm8: u8) -> bool {
false
}
}
#[derive(Debug, Clone, Default)]
pub struct X86XOP3DNowEncoding {
pub xop_catalog: HashMap<(u8, u8), &'static str>,
pub tdnow_catalog: HashMap<u8, TdNowInstruction>,
pub default_xop_vl: VexVectorLength,
}
impl X86XOP3DNowEncoding {
pub fn new() -> Self {
let mut xop_catalog = HashMap::new();
for (name, op, _) in XopInstructionCatalog::map8_instructions() {
xop_catalog.insert((XOP_MAP8, op), name);
}
for (name, op, _) in XopInstructionCatalog::map8_shift_instructions() {
xop_catalog.insert((XOP_MAP8, op), name);
}
for (name, op, _) in XopInstructionCatalog::map9_instructions() {
xop_catalog.insert((XOP_MAP9, op), name);
}
let mut tdnow_catalog = HashMap::new();
for inst in TdNowCatalog::all_instructions() {
tdnow_catalog.insert(inst.imm8, inst);
}
Self {
xop_catalog,
tdnow_catalog,
default_xop_vl: VexVectorLength::L128,
}
}
pub fn xop_config(
&self,
map: XopOpcodeMap,
prefix: VexMandatoryPrefix,
vl: VexVectorLength,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
w: bool,
) -> XopPrefixConfig {
XopPrefixConfig::from_registers(map, prefix, vl, src1_reg, dest_reg, src2_reg, None, w)
}
pub fn encode_xop_prefix(&self, config: &XopPrefixConfig) -> Vec<u8> {
XopPrefixBuilder::build_vec(config)
}
pub fn encode_xop_rrr(
&self,
map: XopOpcodeMap,
prefix: VexMandatoryPrefix,
vl: VexVectorLength,
opcode: u8,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
) -> XopEncodedInstruction {
let config = self.xop_config(map, prefix, vl, dest_reg, src1_reg, src2_reg, false);
let xop_bytes = XopPrefixBuilder::build_vec(&config);
let modrm = 0xC0 | ((dest_reg & 0x07) << 3) | (src2_reg & 0x07);
let mnemonic = self.xop_catalog.get(&(map.mmmmm(), opcode)).copied();
XopEncodedInstruction {
xop_prefix: xop_bytes,
opcode: vec![opcode],
modrm: Some(modrm),
sib: None,
displacement: vec![],
immediate: vec![],
map,
mnemonic,
}
}
pub fn encode_xop_with_imm(
&self,
map: XopOpcodeMap,
prefix: VexMandatoryPrefix,
vl: VexVectorLength,
opcode: u8,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
imm8: u8,
) -> XopEncodedInstruction {
let mut instr = self.encode_xop_rrr(map, prefix, vl, opcode, dest_reg, src1_reg, src2_reg);
instr.immediate = vec![imm8];
instr
}
pub fn encode_named_xop(
&self,
mnemonic: &str,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
) -> Option<XopEncodedInstruction> {
for (&(map_val, opcode), &name) in &self.xop_catalog {
if name == mnemonic {
let map = XopOpcodeMap::from_mmmmm(map_val)?;
return Some(self.encode_xop_rrr(
map,
VexMandatoryPrefix::None,
self.default_xop_vl,
opcode,
dest_reg,
src1_reg,
src2_reg,
));
}
}
None
}
pub fn lookup_xop(&self, map: u8, opcode: u8) -> Option<&'static str> {
XopInstructionCatalog::lookup(map, opcode)
}
pub fn decode_xop(&self, bytes: &[u8]) -> Option<(XopDecodedPrefix, usize)> {
XopDecodedPrefix::decode_from_slice(bytes)
}
pub fn xop_instruction_length(
opcode_size: u8,
has_modrm: bool,
has_sib: bool,
disp_size: u8,
imm_size: u8,
) -> u16 {
let prefix: u16 = 3;
let op: u16 = opcode_size as u16;
let modrm: u16 = if has_modrm { 1 } else { 0 };
let sib: u16 = if has_sib { 1 } else { 0 };
let disp: u16 = disp_size as u16;
let imm: u16 = imm_size as u16;
prefix + op + modrm + sib + disp + imm
}
pub fn encode_tdnow(
&self,
imm8: u8,
dest_reg: u8,
src_reg_or_mem: u8,
is_memory: bool,
disp: Option<i32>,
) -> TdNowEncodedInstruction {
let mut instr = TdNowEncodedInstruction::new(imm8);
let modrm = if is_memory {
if let Some(d) = disp {
if d >= -128 && d <= 127 {
instr.displacement = vec![d as u8];
0x40 | ((dest_reg & 0x07) << 3) | (src_reg_or_mem & 0x07)
} else {
instr.displacement = d.to_le_bytes().to_vec();
0x80 | ((dest_reg & 0x07) << 3) | (src_reg_or_mem & 0x07)
}
} else {
0x00 | ((dest_reg & 0x07) << 3) | (src_reg_or_mem & 0x07)
}
} else {
0xC0 | ((dest_reg & 0x07) << 3) | (src_reg_or_mem & 0x07)
};
instr.modrm = Some(modrm);
instr.bytes = instr.assemble();
instr
}
pub fn encode_tdnow_rr(&self, imm8: u8, dest_mm: u8, src_mm: u8) -> TdNowEncodedInstruction {
self.encode_tdnow(imm8, dest_mm, src_mm, false, None)
}
pub fn encode_femms(&self) -> Vec<u8> {
FemmsEncoder::encode()
}
pub fn encode_prefetch(
&self,
variant: u8,
mem_base: u8,
sib: Option<u8>,
disp: &[u8],
) -> Vec<u8> {
let modrm = if disp.is_empty() {
0x00 | (mem_base & 0x07)
} else if disp.len() == 1 {
0x40 | (mem_base & 0x07)
} else {
0x80 | (mem_base & 0x07)
};
PrefetchEncoder::encode(variant, modrm, sib, disp)
}
pub fn lookup_tdnow(&self, imm8: u8) -> Option<&TdNowInstruction> {
self.tdnow_catalog.get(&imm8)
}
pub fn is_valid_tdnow_opcode(&self, imm8: u8) -> bool {
self.tdnow_catalog.contains_key(&imm8)
}
pub fn is_tdnow_prefix(bytes: &[u8]) -> bool {
bytes.len() >= 2 && bytes[0] == TDNOW_ESCAPE_0F && bytes[1] == TDNOW_ESCAPE_SECOND
}
pub fn is_valid_mmx_reg(&self, reg: u8) -> bool {
TdNowRegisterPairing::is_valid_mmx_reg(reg)
}
pub fn form_pair(&self, low: u8) -> Option<(u8, u8)> {
TdNowRegisterPairing::form_pair(low)
}
pub fn all_pairs(&self) -> Vec<(u8, u8)> {
TdNowRegisterPairing::all_pairs()
}
}
#[derive(Debug, Clone)]
pub struct XopTdNowAssemblyPrinter;
impl XopTdNowAssemblyPrinter {
pub fn format_xop_prefix(decoded: &XopDecodedPrefix) -> String {
format!(
"XOP R={} X={} B={} mmmmm={:05b} W={} vvvv={:04b} L={} pp={:02b}",
decoded.r as u8,
decoded.x as u8,
decoded.b as u8,
decoded.mmmmm,
decoded.w as u8,
decoded.vvvv,
decoded.l as u8,
decoded.pp,
)
}
pub fn format_tdnow(instr: &TdNowEncodedInstruction) -> String {
let mnemonic = instr.mnemonic_str();
let bytes = instr.assemble();
let mut s = format!("3DNow! {} [", mnemonic);
for (i, b) in bytes.iter().enumerate() {
if i > 0 {
s.push(' ');
}
s.push_str(&format!("{:02X}", b));
}
s.push(']');
s
}
pub fn print_tdnow_intel(instr: &TdNowEncodedInstruction) -> String {
let mnemonic = instr.mnemonic_str();
if let Some(modrm) = instr.modrm {
let dest = (modrm >> 3) & 0x07;
let src = modrm & 0x07;
let is_mem = (modrm >> 6) != 3;
if is_mem {
format!("{} mm{}, [mem]", mnemonic, dest)
} else {
format!("{} mm{}, mm{}", mnemonic, dest, src)
}
} else {
format!("{} (no operands)", mnemonic)
}
}
pub fn print_prefetch_intel(variant: u8, modrm: u8) -> String {
let name = PrefetchEncoder::variant_name(variant);
let base = modrm & 0x07;
format!("{} [r{}]", name, base)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xop_constants() {
assert_eq!(XOP_PREFIX, 0x8F);
assert_eq!(XOP_MAP8, 0x08);
assert_eq!(XOP_MAP9, 0x09);
assert_eq!(XOP_MAPA, 0x0A);
}
#[test]
fn test_xop_opcode_map() {
assert_eq!(XopOpcodeMap::Map8.mmmmm(), 8);
assert_eq!(XopOpcodeMap::Map9.mmmmm(), 9);
assert_eq!(XopOpcodeMap::MapA.mmmmm(), 10);
assert_eq!(XopOpcodeMap::from_mmmmm(8), Some(XopOpcodeMap::Map8));
assert_eq!(XopOpcodeMap::from_mmmmm(9), Some(XopOpcodeMap::Map9));
assert_eq!(XopOpcodeMap::from_mmmmm(10), Some(XopOpcodeMap::MapA));
assert_eq!(XopOpcodeMap::from_mmmmm(1), None);
assert_eq!(XopOpcodeMap::from_mmmmm(7), None);
}
#[test]
fn test_xop_prefix_config_default() {
let cfg = XopPrefixConfig::default();
assert_eq!(cfg.mmmmm, XOP_MAP8);
assert_eq!(cfg.pp, VEX_PP_NONE);
assert!(!cfg.w);
assert!(!cfg.l);
}
#[test]
fn test_xop_prefix_config_prefix_byte_count() {
let cfg = XopPrefixConfig::new();
assert_eq!(cfg.prefix_byte_count(), 3);
}
#[test]
fn test_xop_prefix_config_from_registers() {
let cfg = XopPrefixConfig::from_registers(
XopOpcodeMap::Map9,
VexMandatoryPrefix::P66,
VexVectorLength::L128,
1, 8, 9, Some(10), true,
);
assert!(cfg.r); assert!(cfg.x); assert!(cfg.b); assert!(cfg.w);
assert_eq!(cfg.mmmmm, XOP_MAP9);
assert_eq!(cfg.pp, VexMandatoryPrefix::P66.pp_bits());
}
#[test]
fn test_build_xop_basic() {
let bytes = XopPrefixBuilder::build(
true,
true,
true, XOP_MAP8, false, 0x0E, false, VEX_PP_NONE, );
assert_eq!(bytes[0], 0x8F);
assert_eq!(bytes[1], 0x08);
assert_eq!(bytes[2], 0x70);
}
#[test]
fn test_build_xop_with_w_and_l() {
let bytes = XopPrefixBuilder::build(
false, false, false, XOP_MAP9, true, 0x07, true, VEX_PP_66, );
assert_eq!(bytes[1], 0xE9);
assert_eq!(bytes[2], 0xBD);
}
#[test]
fn test_build_xop_from_config() {
let cfg = XopPrefixConfig::new()
.with_map(XopOpcodeMap::Map8)
.with_prefix(VexMandatoryPrefix::P66)
.with_vector_length(VexVectorLength::L128)
.with_vvvv(1)
.with_dest_reg(0)
.with_base_reg(2);
let bytes = XopPrefixBuilder::build_from_config(&cfg);
assert_eq!(bytes[0], 0x8F);
assert_eq!(bytes.len(), 3);
}
#[test]
fn test_build_xop_from_regs() {
let bytes = XopPrefixBuilder::build_from_regs(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
0,
1,
2,
false,
);
assert_eq!(bytes[0], 0x8F);
assert_eq!(bytes.len(), 3);
}
#[test]
fn test_decode_xop() {
let decoded = XopDecodedPrefix::decode(0x08, 0x70);
assert!(decoded.r);
assert!(decoded.x);
assert!(decoded.b);
assert_eq!(decoded.mmmmm, XOP_MAP8);
assert!(!decoded.w);
assert_eq!(decoded.vvvv, 0x0E);
assert_eq!(decoded.true_vvvv(), 1);
assert!(!decoded.l);
assert_eq!(decoded.pp, VEX_PP_NONE);
}
#[test]
fn test_decode_xop_extended() {
let decoded = XopDecodedPrefix::decode(0xE9, 0xBD);
assert!(!decoded.r);
assert!(!decoded.x);
assert!(!decoded.b);
assert_eq!(decoded.mmmmm, XOP_MAP9);
assert!(decoded.w);
assert_eq!(decoded.vvvv, 0x07);
assert_eq!(decoded.true_vvvv(), 8);
assert!(decoded.l);
assert_eq!(decoded.pp, VEX_PP_66);
}
#[test]
fn test_decode_xop_from_slice() {
let (d, len) = XopDecodedPrefix::decode_from_slice(&[0x8F, 0x08, 0x70]).unwrap();
assert_eq!(len, 3);
assert!(XopDecodedPrefix::decode_from_slice(&[0xC4, 0x08, 0x70]).is_none()); assert!(XopDecodedPrefix::decode_from_slice(&[]).is_none());
}
#[test]
fn test_xop_catalog_map8() {
let insts = XopInstructionCatalog::map8_instructions();
assert!(insts.len() >= 20);
assert!(insts.iter().any(|(name, _, _)| *name == "vphaddbd"));
}
#[test]
fn test_xop_catalog_map8_shifts() {
let insts = XopInstructionCatalog::map8_shift_instructions();
assert!(insts.len() >= 10);
assert!(insts.iter().any(|(name, _, _)| *name == "vprotb"));
}
#[test]
fn test_xop_catalog_map9() {
let insts = XopInstructionCatalog::map9_instructions();
assert!(insts.len() >= 10);
assert!(insts.iter().any(|(name, _, _)| *name == "vpcmov"));
}
#[test]
fn test_xop_catalog_lookup() {
assert_eq!(XopInstructionCatalog::lookup(8, 0xC2), Some("vphaddbd"));
assert_eq!(XopInstructionCatalog::lookup(8, 0x90), Some("vprotb"));
assert_eq!(XopInstructionCatalog::lookup(9, 0xA0), Some("vpcmov"));
assert_eq!(XopInstructionCatalog::lookup(8, 0x00), None);
assert_eq!(XopInstructionCatalog::lookup(1, 0x00), None);
}
#[test]
fn test_tdnow_constants() {
assert_eq!(TDNOW_ESCAPE_0F, 0x0F);
assert_eq!(TDNOW_ESCAPE_SECOND, 0x0F);
assert_eq!(FEMMS_OPCODE, [0x0F, 0x0E]);
}
#[test]
fn test_tdnow_opcodes() {
assert_eq!(tdnow_opcodes::PFADD, 0x9E);
assert_eq!(tdnow_opcodes::PFMUL, 0xB4);
assert_eq!(tdnow_opcodes::PFSUB, 0x9A);
assert_eq!(tdnow_opcodes::PAVGUSB, 0xBF);
}
#[test]
fn test_tdnow_catalog_size() {
assert_eq!(TdNowCatalog::all_instructions().len(), 23);
}
#[test]
fn test_tdnow_catalog_lookup() {
let inst = TdNowCatalog::lookup(tdnow_opcodes::PFADD).unwrap();
assert_eq!(inst.mnemonic, "pfadd");
assert_eq!(inst.description, "Packed Float Add");
let inst = TdNowCatalog::lookup(tdnow_opcodes::PFRCP).unwrap();
assert_eq!(inst.mnemonic, "pfrcp");
assert!(TdNowCatalog::lookup(0x00).is_none());
}
#[test]
fn test_tdnow_is_valid_opcode() {
assert!(TdNowCatalog::is_valid_tdnow_opcode(tdnow_opcodes::PFADD));
assert!(!TdNowCatalog::is_valid_tdnow_opcode(0x00));
assert!(!TdNowCatalog::is_valid_tdnow_opcode(0xFF));
}
#[test]
fn test_femms_encode() {
let bytes = FemmsEncoder::encode();
assert_eq!(bytes, vec![0x0F, 0x0E]);
assert_eq!(FemmsEncoder::LENGTH, 2);
}
#[test]
fn test_femms_detect() {
assert!(FemmsEncoder::is_femms(&[0x0F, 0x0E]));
assert!(!FemmsEncoder::is_femms(&[0x0F, 0x0F]));
assert!(!FemmsEncoder::is_femms(&[0x0F]));
}
#[test]
fn test_prefetch_encode() {
let bytes = PrefetchEncoder::encode(0, 0x00, None, &[]);
assert_eq!(bytes, vec![0x0F, 0x0D, 0x00]);
}
#[test]
fn test_prefetchw_encode() {
let bytes = PrefetchEncoder::encode(1, 0x00, None, &[]);
assert_eq!(bytes, vec![0x0F, 0x0D, 0x08]); }
#[test]
fn test_prefetch_variant_names() {
assert_eq!(PrefetchEncoder::variant_name(0), "prefetch");
assert_eq!(PrefetchEncoder::variant_name(1), "prefetchw");
assert_eq!(PrefetchEncoder::variant_name(2), "prefetcht0");
assert_eq!(PrefetchEncoder::variant_name(3), "prefetcht1");
assert_eq!(PrefetchEncoder::variant_name(4), "prefetcht2");
assert_eq!(PrefetchEncoder::variant_name(5), "prefetchnta");
}
#[test]
fn test_prefetch_with_disp() {
let bytes = PrefetchEncoder::encode_prefetcht0(0x00, None, &[0x04]);
assert_eq!(&bytes[0..2], &[0x0F, 0x0D]);
assert_eq!(bytes[2], 0x50);
assert_eq!(bytes[3], 0x04);
}
#[test]
fn test_tdnow_register_pairing() {
assert!(TdNowRegisterPairing::is_valid_mmx_reg(0));
assert!(TdNowRegisterPairing::is_valid_mmx_reg(7));
assert!(!TdNowRegisterPairing::is_valid_mmx_reg(8));
assert_eq!(TdNowRegisterPairing::form_pair(0), Some((0, 1)));
assert_eq!(TdNowRegisterPairing::form_pair(6), Some((6, 7)));
assert_eq!(TdNowRegisterPairing::form_pair(7), None);
assert!(TdNowRegisterPairing::is_valid_pair(0, 1));
assert!(!TdNowRegisterPairing::is_valid_pair(0, 2));
assert!(!TdNowRegisterPairing::is_valid_pair(7, 8));
}
#[test]
fn test_tdnow_register_pairing_all() {
let pairs = TdNowRegisterPairing::all_pairs();
assert_eq!(pairs.len(), 7);
assert_eq!(pairs[0], (0, 1));
assert_eq!(pairs[6], (6, 7));
}
#[test]
fn test_tdnow_pair_name() {
assert_eq!(TdNowRegisterPairing::pair_name(0, 1), "mm0/mm1");
assert_eq!(TdNowRegisterPairing::pair_name(3, 4), "mm3/mm4");
}
#[test]
fn test_tdnow_encoded_instruction_new() {
let instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
assert_eq!(instr.imm8, tdnow_opcodes::PFADD);
assert_eq!(instr.mnemonic, Some("pfadd"));
}
#[test]
fn test_tdnow_assemble_rr() {
let mut instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
instr.modrm = Some(0xC0 | ((0 & 0x07) << 3) | (1 & 0x07));
instr.bytes = instr.assemble();
assert_eq!(instr.bytes, vec![0x0F, 0x0F, 0xC1, tdnow_opcodes::PFADD]);
}
#[test]
fn test_tdnow_total_length() {
let instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
assert_eq!(instr.total_length(), 3); }
#[test]
fn test_xop3dnow_encoding_new() {
let enc = X86XOP3DNowEncoding::new();
assert!(!enc.xop_catalog.is_empty());
assert_eq!(enc.tdnow_catalog.len(), 23);
assert_eq!(enc.default_xop_vl, VexVectorLength::L128);
}
#[test]
fn test_encode_xop_rrr() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_xop_rrr(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
0xC2, 0,
1,
2,
);
assert_eq!(instr.opcode, vec![0xC2]);
assert!(instr.modrm.is_some());
assert_eq!(instr.xop_prefix.len(), 3);
assert!(instr.is_valid());
assert_eq!(instr.mnemonic, Some("vphaddbd"));
}
#[test]
fn test_encode_xop_with_imm() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_xop_with_imm(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
0x90, 0,
1,
2,
3, );
assert_eq!(instr.immediate, vec![3]);
}
#[test]
fn test_encode_named_xop() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_named_xop("vprotb", 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![0x90]);
assert_eq!(instr.mnemonic, Some("vprotb"));
assert!(enc.encode_named_xop("nonexistent", 0, 1, 2).is_none());
}
#[test]
fn test_encode_tdnow_rr() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow_rr(tdnow_opcodes::PFADD, 0, 1);
assert_eq!(instr.imm8, tdnow_opcodes::PFADD);
assert!(instr.modrm.is_some());
assert_eq!(instr.modrm.unwrap(), 0xC1);
}
#[test]
fn test_encode_femms() {
let enc = X86XOP3DNowEncoding::new();
let bytes = enc.encode_femms();
assert_eq!(bytes, vec![0x0F, 0x0E]);
}
#[test]
fn test_encode_prefetch() {
let enc = X86XOP3DNowEncoding::new();
let bytes = enc.encode_prefetch(1, 0, None, &[]); assert_eq!(bytes, vec![0x0F, 0x0D, 0x08]); }
#[test]
fn test_lookup_tdnow() {
let enc = X86XOP3DNowEncoding::new();
let inst = enc.lookup_tdnow(tdnow_opcodes::PFMUL).unwrap();
assert_eq!(inst.mnemonic, "pfmul");
assert!(enc.lookup_tdnow(0x00).is_none());
}
#[test]
fn test_is_valid_tdnow_opcode() {
let enc = X86XOP3DNowEncoding::new();
assert!(enc.is_valid_tdnow_opcode(tdnow_opcodes::PFSUB));
assert!(!enc.is_valid_tdnow_opcode(0xFF));
}
#[test]
fn test_is_tdnow_prefix() {
assert!(X86XOP3DNowEncoding::is_tdnow_prefix(&[0x0F, 0x0F, 0x9E]));
assert!(!X86XOP3DNowEncoding::is_tdnow_prefix(&[0x0F, 0x0E]));
assert!(!X86XOP3DNowEncoding::is_tdnow_prefix(&[0x0F]));
}
#[test]
fn test_form_pair() {
let enc = X86XOP3DNowEncoding::new();
assert_eq!(enc.form_pair(0), Some((0, 1)));
assert_eq!(enc.form_pair(6), Some((6, 7)));
assert_eq!(enc.form_pair(7), None);
}
#[test]
fn test_all_pairs() {
let enc = X86XOP3DNowEncoding::new();
let pairs = enc.all_pairs();
assert_eq!(pairs.len(), 7);
}
#[test]
fn test_xop_instruction_length() {
let len = X86XOP3DNowEncoding::xop_instruction_length(1, true, false, 0, 0);
assert_eq!(len, 5);
let len = X86XOP3DNowEncoding::xop_instruction_length(1, true, true, 4, 1);
assert_eq!(len, 11);
}
#[test]
fn test_assembly_printer_xop() {
let decoded = XopDecodedPrefix::decode(0x08, 0x70);
let s = XopTdNowAssemblyPrinter::format_xop_prefix(&decoded);
assert!(s.contains("XOP"));
assert!(s.contains("R="));
assert!(s.contains("mmmmm="));
}
#[test]
fn test_assembly_printer_tdnow() {
let instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
let s = XopTdNowAssemblyPrinter::format_tdnow(&instr);
assert!(s.contains("3DNow!"));
assert!(s.contains("pfadd"));
}
#[test]
fn test_assembly_printer_tdnow_intel() {
let mut instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
instr.modrm = Some(0xC0 | ((0 & 0x07) << 3) | (1 & 0x07));
let s = XopTdNowAssemblyPrinter::print_tdnow_intel(&instr);
assert!(s.contains("pfadd"));
assert!(s.contains("mm0"));
assert!(s.contains("mm1"));
}
#[test]
fn test_assembly_printer_prefetch() {
let s = XopTdNowAssemblyPrinter::print_prefetch_intel(1, 0x00);
assert!(s.contains("prefetchw"));
assert!(s.contains("[r0]"));
}
#[test]
fn test_roundtrip_xop_encode_decode() {
let bytes = XopPrefixBuilder::build_from_regs(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
0,
1,
2,
false,
);
let (decoded, len) = XopDecodedPrefix::decode_from_slice(&bytes).unwrap();
assert_eq!(len, 3);
assert_eq!(decoded.mmmmm, XOP_MAP8);
assert_eq!(decoded.true_vvvv(), 1);
}
#[test]
fn test_full_xop_instruction_assemble() {
let config = XopPrefixConfig::new()
.with_map(XopOpcodeMap::Map8)
.with_vvvv(1)
.with_dest_reg(0)
.with_base_reg(2);
let mut instr = XopEncodedInstruction::new(XopOpcodeMap::Map8);
instr.xop_prefix = XopPrefixBuilder::build_vec(&config);
instr.opcode = vec![0xC2]; instr.modrm = Some(0xC0 | ((0 & 0x07) << 3) | (2 & 0x07));
let assembled = instr.assemble();
assert_eq!(assembled.len(), 5);
assert!(instr.is_valid());
}
#[test]
fn test_full_tdnow_assemble() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow_rr(tdnow_opcodes::PFMUL, 2, 3);
let assembled = instr.assemble();
assert_eq!(assembled.len(), 4);
assert!(instr.is_valid());
}
#[test]
fn test_all_tdnow_opcodes_valid() {
for inst in TdNowCatalog::all_instructions() {
assert!(inst.imm8 != 0, "Zero immediate for {}", inst.mnemonic);
}
}
#[test]
fn test_all_xop_catalog_entries() {
let enc = X86XOP3DNowEncoding::new();
for (name, op, _) in XopInstructionCatalog::map8_instructions() {
let result = enc.encode_named_xop(name, 0, 1, 2);
assert!(
result.is_some(),
"Failed to encode XOP instruction: {}",
name
);
let instr = result.unwrap();
assert_eq!(instr.opcode[0], op);
}
}
#[test]
fn test_no_duplicate_tdnow_immediates() {
let insts = TdNowCatalog::all_instructions();
let mut seen = std::collections::HashSet::new();
for inst in &insts {
assert!(
seen.insert(inst.imm8),
"Duplicate 3DNow! immediate: {:02X}",
inst.imm8
);
}
}
#[test]
fn test_xop_map9_vpcmov() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_named_xop("vpcmov", 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![0xA0]);
assert_eq!(instr.map, XopOpcodeMap::Map9);
}
#[test]
fn test_xop_all_map8_mnemonics_encode() {
let enc = X86XOP3DNowEncoding::new();
for (name, _, _) in XopInstructionCatalog::map8_instructions() {
let result = enc.encode_named_xop(name, 0, 1, 2);
assert!(
result.is_some(),
"XOP Map8 mnemonic should encode: {}",
name
);
}
for (name, _, _) in XopInstructionCatalog::map8_shift_instructions() {
let result = enc.encode_named_xop(name, 0, 1, 2);
assert!(
result.is_some(),
"XOP Map8 shift mnemonic should encode: {}",
name
);
}
}
#[test]
fn test_xop_all_map9_mnemonics_encode() {
let enc = X86XOP3DNowEncoding::new();
for (name, _, _) in XopInstructionCatalog::map9_instructions() {
let result = enc.encode_named_xop(name, 0, 1, 2);
assert!(
result.is_some(),
"XOP Map9 mnemonic should encode: {}",
name
);
}
}
#[test]
fn test_xop_map8_vphaddbd_opcode() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_named_xop("vphaddbd", 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![0xC2]);
assert_eq!(instr.map, XopOpcodeMap::Map8);
}
#[test]
fn test_xop_map8_vprotb_opcode() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_named_xop("vprotb", 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![0x90]);
}
#[test]
fn test_xop_map9_vfrczps_opcode() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_named_xop("vfrczps", 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![0x80]);
assert_eq!(instr.map, XopOpcodeMap::Map9);
}
#[test]
fn test_xop_map9_vpcomb_opcode() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_named_xop("vpcomb", 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![0x8C]);
assert_eq!(instr.map, XopOpcodeMap::Map9);
}
#[test]
fn test_xop_with_extended_registers() {
let cfg = XopPrefixConfig::from_registers(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
10, 12, 14, None,
true, );
assert!(cfg.r); assert!(cfg.b); assert!(cfg.w);
assert_eq!(cfg.vvvv, 5);
}
#[test]
fn test_xop_with_l256() {
let cfg = XopPrefixConfig::new().with_vector_length(VexVectorLength::L256);
assert!(cfg.l);
}
#[test]
fn test_xop_config_all_maps() {
for map in [XopOpcodeMap::Map8, XopOpcodeMap::Map9, XopOpcodeMap::MapA] {
let cfg = XopPrefixConfig::new().with_map(map);
assert_eq!(cfg.mmmmm, map.mmmmm());
}
}
#[test]
fn test_xop_prefix_decode_roundtrip() {
let original = XopPrefixConfig::from_registers(
XopOpcodeMap::Map9,
VexMandatoryPrefix::PF2,
VexVectorLength::L256,
3,
8,
9,
Some(10),
true,
);
let bytes = XopPrefixBuilder::build_vec(&original);
let (decoded, _) = XopDecodedPrefix::decode_from_slice(&bytes).unwrap();
let reconstructed = decoded.to_config();
assert_eq!(reconstructed.r, original.r);
assert_eq!(reconstructed.x, original.x);
assert_eq!(reconstructed.b, original.b);
assert_eq!(reconstructed.mmmmm, original.mmmmm);
assert_eq!(reconstructed.w, original.w);
assert_eq!(reconstructed.vvvv, original.vvvv);
assert_eq!(reconstructed.l, original.l);
assert_eq!(reconstructed.pp, original.pp);
}
#[test]
fn test_tdnow_encode_all_opcodes() {
let enc = X86XOP3DNowEncoding::new();
for inst in TdNowCatalog::all_instructions() {
let instr = enc.encode_tdnow_rr(inst.imm8, 0, 1);
assert!(instr.is_valid());
assert_eq!(instr.imm8, inst.imm8);
}
}
#[test]
fn test_tdnow_encode_memory_operand() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow(tdnow_opcodes::PFADD, 0, 0, true, None);
assert!(instr.is_valid());
assert!(instr.modrm.is_some());
assert_eq!(instr.modrm.unwrap(), 0x00);
assert!(instr.displacement.is_empty());
}
#[test]
fn test_tdnow_encode_memory_with_disp8() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow(tdnow_opcodes::PFADD, 0, 0, true, Some(4));
assert!(instr.is_valid());
assert_eq!(instr.modrm.unwrap(), 0x40);
assert_eq!(instr.displacement, vec![4u8]);
}
#[test]
fn test_tdnow_encode_memory_with_disp32() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow(tdnow_opcodes::PFADD, 0, 0, true, Some(1024));
assert!(instr.is_valid());
assert_eq!(instr.modrm.unwrap(), 0x80);
assert_eq!(instr.displacement, 1024i32.to_le_bytes().to_vec());
}
#[test]
fn test_tdnow_encode_memory_negative_disp8() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow(tdnow_opcodes::PFADD, 0, 0, true, Some(-4));
assert!(instr.is_valid());
assert_eq!(instr.modrm.unwrap(), 0x40);
assert_eq!(instr.displacement, vec![(-4i8) as u8]);
}
#[test]
fn test_tdnow_mnemonic_lookup_all() {
for inst in TdNowCatalog::all_instructions() {
let looked_up = TdNowCatalog::lookup(inst.imm8);
assert!(looked_up.is_some());
assert_eq!(looked_up.unwrap().mnemonic, inst.mnemonic);
}
}
#[test]
fn test_tdnow_display() {
let instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
let s = format!("{}", instr);
assert!(s.contains("3DNow!"));
}
#[test]
fn test_tdnow_invalid_opcode_lookup() {
let enc = X86XOP3DNowEncoding::new();
assert!(!enc.is_valid_tdnow_opcode(0x00));
assert!(!enc.is_valid_tdnow_opcode(0xFF));
assert!(!enc.is_valid_tdnow_opcode(0x42));
}
#[test]
fn test_prefetch_all_variants() {
for (name, reg) in PrefetchEncoder::PREFETCH_VARIANTS.iter() {
let bytes = PrefetchEncoder::encode(*reg, 0x00, None, &[]);
assert_eq!(bytes[0], 0x0F);
assert_eq!(bytes[1], 0x0D);
let reg_in_modrm = (bytes[2] >> 3) & 0x07;
assert_eq!(reg_in_modrm, *reg, "Reg field mismatch for {}", name);
}
}
#[test]
fn test_prefetch_with_sib() {
let sib = 0x81; let bytes = PrefetchEncoder::encode_prefetcht0(0x04, Some(sib), &[]);
assert_eq!(bytes[0], 0x0F);
assert_eq!(bytes[1], 0x0D);
assert_eq!(bytes[2], 0x14);
assert_eq!(bytes[3], sib);
}
#[test]
fn test_prefetch_variant_names_all() {
for reg in 0..8u8 {
let name = PrefetchEncoder::variant_name(reg);
assert!(!name.is_empty());
}
}
#[test]
fn test_prefetch_encode_convenience() {
let bytes_t0 = PrefetchEncoder::encode_prefetcht0(0x00, None, &[]);
assert_eq!(&bytes_t0[0..2], &[0x0F, 0x0D]);
assert_eq!((bytes_t0[2] >> 3) & 0x07, 2);
let bytes_t1 = PrefetchEncoder::encode_prefetcht1(0x00, None, &[]);
assert_eq!((bytes_t1[2] >> 3) & 0x07, 3);
let bytes_t2 = PrefetchEncoder::encode_prefetcht2(0x00, None, &[]);
assert_eq!((bytes_t2[2] >> 3) & 0x07, 4);
let bytes_nta = PrefetchEncoder::encode_prefetchnta(0x00, None, &[]);
assert_eq!((bytes_nta[2] >> 3) & 0x07, 5);
let bytes_w = PrefetchEncoder::encode_prefetchw(0x00, None, &[]);
assert_eq!((bytes_w[2] >> 3) & 0x07, 1);
}
#[test]
fn test_xop_encoded_instruction_display() {
let mut instr = XopEncodedInstruction::new(XopOpcodeMap::Map8);
instr.xop_prefix = vec![0x8F, 0x08, 0x70];
instr.opcode = vec![0xC2];
instr.modrm = Some(0xC0);
let s = format!("{}", instr);
assert!(s.contains("XOP"));
assert!(s.contains("8F"));
}
#[test]
fn test_xop_encoded_instruction_assemble_roundtrip() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_xop_rrr(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
0xC2,
0,
1,
2,
);
let assembled = instr.assemble();
assert!(assembled.len() >= 4);
assert_eq!(assembled[0], XOP_PREFIX);
}
#[test]
fn test_xop_catalog_if_let_lookup() {
assert!(XopInstructionCatalog::lookup(0, 0x00).is_none());
assert!(XopInstructionCatalog::lookup(255, 0x00).is_none());
assert!(XopInstructionCatalog::lookup(8, 255).is_none());
}
#[test]
fn test_xop_decoded_effective_regs() {
let decoded = XopDecodedPrefix::decode(0x08, 0x70);
assert_eq!(decoded.effective_reg_r(3), 3);
let decoded = XopDecodedPrefix::decode(0xE8, 0x70);
assert_eq!(decoded.effective_reg_r(3), 11);
}
#[test]
fn test_xop_decoded_display() {
let d = XopDecodedPrefix::decode(0x08, 0x70);
let s = format!("{}", d);
assert!(s.contains("XOP"));
assert!(s.contains("R="));
assert!(s.contains("mmmmm="));
}
#[test]
fn test_xop_opcode_map_display() {
assert_eq!(format!("{}", XopOpcodeMap::Map8), "XOP8");
assert_eq!(format!("{}", XopOpcodeMap::Map9), "XOP9");
assert_eq!(format!("{}", XopOpcodeMap::MapA), "XOPA");
}
#[test]
fn test_tdnow_encoded_instruction_defaults() {
let instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
assert_eq!(instr.imm8, tdnow_opcodes::PFADD);
assert!(instr.modrm.is_none());
assert!(instr.displacement.is_empty());
assert_eq!(instr.mnemonic_str(), "pfadd");
}
#[test]
fn test_tdnow_unknown_opcode_mnemonic() {
let instr = TdNowEncodedInstruction::new(0xFF);
assert_eq!(instr.mnemonic_str(), "???");
}
#[test]
fn test_register_pairing_can_form_pair() {
for reg in 0..7u8 {
assert!(TdNowRegisterPairing::can_form_pair(reg));
}
assert!(!TdNowRegisterPairing::can_form_pair(7));
assert!(!TdNowRegisterPairing::can_form_pair(8));
}
#[test]
fn test_register_pairing_is_valid_pair_edge() {
assert!(TdNowRegisterPairing::is_valid_pair(0, 1));
assert!(TdNowRegisterPairing::is_valid_pair(6, 7));
assert!(!TdNowRegisterPairing::is_valid_pair(0, 0));
assert!(!TdNowRegisterPairing::is_valid_pair(1, 0));
assert!(!TdNowRegisterPairing::is_valid_pair(7, 8));
assert!(!TdNowRegisterPairing::is_valid_pair(3, 5));
}
#[test]
fn test_xop3dnow_encoding_defaults() {
let enc = X86XOP3DNowEncoding::default();
assert_eq!(enc.default_xop_vl, VexVectorLength::L128);
assert!(enc.xop_catalog.is_empty());
assert!(enc.tdnow_catalog.is_empty());
}
#[test]
fn test_xop3dnow_lookup_tdnow_all() {
let enc = X86XOP3DNowEncoding::new();
assert_eq!(enc.tdnow_catalog.len(), 23);
for imm8 in 0..=255u8 {
let result = enc.lookup_tdnow(imm8);
let is_valid = enc.is_valid_tdnow_opcode(imm8);
assert_eq!(result.is_some(), is_valid);
}
}
#[test]
fn test_encode_named_xop_nonexistent() {
let enc = X86XOP3DNowEncoding::new();
assert!(enc.encode_named_xop("nonexistent_op", 0, 1, 2).is_none());
assert!(enc.encode_named_xop("vaddps", 0, 1, 2).is_none());
}
#[test]
fn test_xop_encode_map9_all() {
let enc = X86XOP3DNowEncoding::new();
for (name, op, _) in XopInstructionCatalog::map9_instructions() {
let instr = enc.encode_named_xop(name, 0, 1, 2);
assert!(instr.is_some(), "Should encode Map9: {}", name);
assert_eq!(instr.unwrap().opcode[0], op);
}
}
#[test]
fn test_xop_instruction_length_variants() {
let len = X86XOP3DNowEncoding::xop_instruction_length(1, false, false, 0, 0);
assert_eq!(len, 4);
let len = X86XOP3DNowEncoding::xop_instruction_length(1, true, true, 0, 0);
assert_eq!(len, 6);
let len = X86XOP3DNowEncoding::xop_instruction_length(1, true, true, 4, 4);
assert_eq!(len, 14);
let len = X86XOP3DNowEncoding::xop_instruction_length(2, true, true, 4, 4);
assert_eq!(len, 15);
}
#[test]
fn test_assembly_printer_tdnow_memory() {
let mut instr = TdNowEncodedInstruction::new(tdnow_opcodes::PFADD);
instr.modrm = Some(0x00);
let s = XopTdNowAssemblyPrinter::print_tdnow_intel(&instr);
assert!(s.contains("pfadd"));
assert!(s.contains("[mem]"));
}
#[test]
fn test_assembly_printer_prefetch_all() {
for reg in 0..8u8 {
let s = XopTdNowAssemblyPrinter::print_prefetch_intel(reg, 0x00);
assert!(!s.is_empty());
}
}
#[test]
fn test_integration_xop_encode_decode_cycle() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_xop_rrr(
XopOpcodeMap::Map8,
VexMandatoryPrefix::P66,
VexVectorLength::L128,
0xC2,
5,
3,
7,
);
let assembled = instr.assemble();
assert_eq!(assembled[0], XOP_PREFIX);
let (decoded, len) = XopDecodedPrefix::decode_from_slice(&assembled).unwrap();
assert_eq!(len, 3);
assert_eq!(decoded.mmmmm, XOP_MAP8);
assert_eq!(decoded.pp, VexMandatoryPrefix::P66.pp_bits());
assert!(!decoded.l);
}
#[test]
fn test_integration_tdnow_full_cycle() {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow_rr(tdnow_opcodes::PFADD, 3, 5);
let bytes = instr.assemble();
assert_eq!(bytes[0], 0x0F);
assert_eq!(bytes[1], 0x0F);
assert_eq!(bytes[2], 0xDD);
assert_eq!(bytes[3], tdnow_opcodes::PFADD);
}
#[test]
fn test_integration_femms_prefetch_together() {
let enc = X86XOP3DNowEncoding::new();
let femms = enc.encode_femms();
assert_eq!(femms.len(), 2);
assert_eq!(femms, [0x0F, 0x0E]);
let prefetchw = enc.encode_prefetch(1, 2, None, &[0x10]);
assert_eq!(prefetchw[0], 0x0F);
assert_eq!(prefetchw[1], 0x0D);
}
#[test]
fn test_xop_rxb_roundtrip() {
for r in [false, true] {
for x in [false, true] {
for b in [false, true] {
let bytes = XopPrefixBuilder::build(r, x, b, XOP_MAP8, false, 0x0F, false, 0);
let d = XopDecodedPrefix::decode(bytes[1], bytes[2]);
assert_eq!(d.r, r);
assert_eq!(d.x, x);
assert_eq!(d.b, b);
}
}
}
}
#[test]
fn test_xop_pp_roundtrip() {
for pp in 0..4u8 {
let bytes = XopPrefixBuilder::build(true, true, true, XOP_MAP8, false, 0x0F, false, pp);
let d = XopDecodedPrefix::decode(bytes[1], bytes[2]);
assert_eq!(d.pp, pp);
}
}
#[test]
fn test_xop_vvvv_roundtrip() {
for reg in 0..16u8 {
let inv = vex_invert_reg(reg);
let bytes = XopPrefixBuilder::build(true, true, true, XOP_MAP8, false, inv, false, 0);
let d = XopDecodedPrefix::decode(bytes[1], bytes[2]);
assert_eq!(d.vvvv, inv);
assert_eq!(d.true_vvvv(), reg);
}
}
#[test]
fn test_xop_wl_roundtrip() {
for w in [false, true] {
for l in [false, true] {
let bytes = XopPrefixBuilder::build(true, true, true, XOP_MAP8, w, 0x0F, l, 0);
let d = XopDecodedPrefix::decode(bytes[1], bytes[2]);
assert_eq!(d.w, w);
assert_eq!(d.l, l);
}
}
}
#[test]
fn test_xop_length_max() {
let len = X86XOP3DNowEncoding::xop_instruction_length(2, true, true, 4, 4);
assert_eq!(len, 15);
}
#[test]
fn test_tdnow_assemble_all_regs() {
for dest in 0..8u8 {
for src in 0..8u8 {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow_rr(tdnow_opcodes::PFADD, dest, src);
let bytes = instr.assemble();
assert_eq!(bytes[0], 0x0F);
assert_eq!(bytes[1], 0x0F);
let expected = 0xC0 | ((dest & 0x07) << 3) | (src & 0x07);
assert_eq!(bytes[2], expected);
assert_eq!(bytes[3], tdnow_opcodes::PFADD);
}
}
}
#[test]
fn test_tdnow_multi_opcode_assemble() {
for &op in &[
tdnow_opcodes::PFADD,
tdnow_opcodes::PFSUB,
tdnow_opcodes::PFMUL,
tdnow_opcodes::PFMAX,
] {
let enc = X86XOP3DNowEncoding::new();
let instr = enc.encode_tdnow_rr(op, 0, 1);
let bytes = instr.assemble();
assert_eq!(bytes[3], op);
}
}
#[test]
fn test_prefetch_disp32() {
let bytes = PrefetchEncoder::encode_prefetcht0(0x05, None, &[0x00, 0x10, 0x00, 0x00]);
assert_eq!(bytes.len(), 7);
assert_eq!(&bytes[3..], &[0x00, 0x10, 0x00, 0x00]);
}
#[test]
fn test_prefetch_negative_disp() {
let bytes = PrefetchEncoder::encode_prefetchw(0x43, None, &[(-8i8) as u8]);
assert_eq!(bytes[2], 0x4B);
assert_eq!(bytes[3], (-8i8) as u8);
}
#[test]
fn test_xop_empty_slice() {
assert!(XopDecodedPrefix::decode_from_slice(&[]).is_none());
assert!(XopDecodedPrefix::decode_from_slice(&[0x8F]).is_none());
}
#[test]
fn test_xop_not_xop() {
assert!(XopDecodedPrefix::decode_from_slice(&[0xC5, 0xF8, 0x00]).is_none());
assert!(XopDecodedPrefix::decode_from_slice(&[0x62, 0xF1, 0x74]).is_none());
}
#[test]
fn test_xop_instr_too_long() {
let mut instr = XopEncodedInstruction::new(XopOpcodeMap::Map8);
instr.xop_prefix = vec![0x8F, 0x08, 0x70];
instr.opcode = vec![0xC2];
instr.modrm = Some(0x00);
instr.displacement = vec![0; 12];
assert!(!instr.is_valid());
}
#[test]
fn test_xop_catalog_total() {
let enc = X86XOP3DNowEncoding::new();
let expected = XopInstructionCatalog::map8_instructions().len()
+ XopInstructionCatalog::map8_shift_instructions().len()
+ XopInstructionCatalog::map9_instructions().len();
assert_eq!(enc.xop_catalog.len(), expected);
}
#[test]
fn test_tdnow_prefix_edge() {
assert!(X86XOP3DNowEncoding::is_tdnow_prefix(&[0x0F, 0x0F]));
assert!(!X86XOP3DNowEncoding::is_tdnow_prefix(&[0x0F]));
assert!(!X86XOP3DNowEncoding::is_tdnow_prefix(&[]));
}
#[test]
fn test_tdnow_all_immediates_unique() {
let insts = TdNowCatalog::all_instructions();
let mut imms: Vec<u8> = insts.iter().map(|i| i.imm8).collect();
imms.sort();
imms.dedup();
assert_eq!(imms.len(), insts.len());
}
#[test]
fn test_tdnow_immediates_in_range() {
for inst in TdNowCatalog::all_instructions() {
assert!(inst.imm8 > 0, "imm8 must be non-zero for {}", inst.mnemonic);
assert!(
inst.imm8 < 0xFF,
"imm8 must be < 0xFF for {}",
inst.mnemonic
);
}
}
#[test]
fn test_tdnow_all_mnemonics_lowercase() {
for inst in TdNowCatalog::all_instructions() {
assert_eq!(inst.mnemonic, inst.mnemonic.to_lowercase());
}
}
#[test]
fn test_tdnow_all_have_descriptions() {
for inst in TdNowCatalog::all_instructions() {
assert!(
!inst.description.is_empty(),
"{} has no description",
inst.mnemonic
);
}
}
#[test]
fn test_tdnow_femms_distinct_from_tdnow() {
assert!(FemmsEncoder::is_femms(&[0x0F, 0x0E]));
assert!(!X86XOP3DNowEncoding::is_tdnow_prefix(&[0x0F, 0x0E]));
}
#[test]
fn test_xop_bulk_encode_map8_instructions() {
let enc = X86XOP3DNowEncoding::new();
for (name, op, _) in XopInstructionCatalog::map8_instructions() {
let instr = enc.encode_named_xop(name, 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![op], "Wrong opcode for {}", name);
assert_eq!(instr.map, XopOpcodeMap::Map8);
assert!(instr.xop_prefix.len() == 3);
assert!(instr.is_valid());
}
}
#[test]
fn test_xop_bulk_encode_map8_shifts() {
let enc = X86XOP3DNowEncoding::new();
for (name, op, _) in XopInstructionCatalog::map8_shift_instructions() {
let instr = enc.encode_named_xop(name, 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![op], "Wrong opcode for {}", name);
assert_eq!(instr.map, XopOpcodeMap::Map8);
assert!(instr.is_valid());
}
}
#[test]
fn test_xop_bulk_encode_map9_instructions() {
let enc = X86XOP3DNowEncoding::new();
for (name, op, _) in XopInstructionCatalog::map9_instructions() {
let instr = enc.encode_named_xop(name, 0, 1, 2).unwrap();
assert_eq!(instr.opcode, vec![op], "Wrong opcode for {}", name);
assert_eq!(instr.map, XopOpcodeMap::Map9);
assert!(instr.is_valid());
}
}
#[test]
fn test_xop_bulk_encode_with_imm() {
let enc = X86XOP3DNowEncoding::new();
for imm in [0u8, 1, 7, 15, 255] {
let instr = enc.encode_xop_with_imm(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
0x90, 0,
1,
2,
imm,
);
assert_eq!(instr.immediate, vec![imm]);
assert!(instr.is_valid());
}
}
#[test]
fn test_xop_not_tdnow() {
let xop_bytes = XopPrefixBuilder::build_from_regs(
XopOpcodeMap::Map8,
VexMandatoryPrefix::None,
VexVectorLength::L128,
0,
1,
2,
false,
);
assert!(!X86XOP3DNowEncoding::is_tdnow_prefix(&xop_bytes));
}
#[test]
fn test_tdnow_not_xop() {
let tdnow_bytes = [
TDNOW_ESCAPE_0F,
TDNOW_ESCAPE_SECOND,
0xC0,
tdnow_opcodes::PFADD,
];
assert!(XopDecodedPrefix::decode_from_slice(&tdnow_bytes).is_none());
}
#[test]
fn test_encoding_catalog_consistency() {
let enc = X86XOP3DNowEncoding::new();
for (&(map_val, opcode), &name) in &enc.xop_catalog {
let instr = enc.encode_named_xop(name, 0, 1, 2);
assert!(instr.is_some(), "XOP should encode: {}", name);
let instr = instr.unwrap();
assert_eq!(instr.opcode[0], opcode);
assert_eq!(instr.map.mmmmm(), map_val);
}
}
#[test]
fn test_xop_lookup_consistency() {
let enc = X86XOP3DNowEncoding::new();
for (&(map, opcode), &name) in &enc.xop_catalog {
let looked_up = enc.lookup_xop(map, opcode);
assert_eq!(looked_up, Some(name));
}
}
#[test]
fn test_xop_opcode_map_from_mmmmm_all() {
for m in 0..=31u8 {
let result = XopOpcodeMap::from_mmmmm(m);
if m == XOP_MAP8 || m == XOP_MAP9 || m == XOP_MAPA {
assert!(result.is_some(), "mmmmm={} should be valid", m);
} else {
assert!(result.is_none(), "mmmmm={} should be invalid", m);
}
}
}
}