#![allow(clippy::too_many_arguments)]
#[allow(unused_imports)]
use alloc::string::String;
#[allow(unused_imports)]
use alloc::string::ToString;
#[allow(unused_imports)]
use alloc::vec;
use alloc::vec::Vec;
use crate::error::AsmError;
#[allow(unused_imports)]
use crate::error::Span;
use crate::ir::*;
#[derive(Clone)]
pub struct InstrBytes {
data: [u8; 32],
len: u8,
}
impl InstrBytes {
#[inline]
pub const fn new() -> Self {
Self {
data: [0; 32],
len: 0,
}
}
#[inline]
pub fn from_slice(src: &[u8]) -> Self {
let mut buf = Self::new();
buf.extend_from_slice(src);
buf
}
#[inline]
pub fn push(&mut self, byte: u8) {
assert!(
(self.len as usize) < 32,
"InstrBytes overflow: cannot push beyond 32 bytes"
);
self.data[self.len as usize] = byte;
self.len += 1;
}
#[inline]
pub fn extend_from_slice(&mut self, bytes: &[u8]) {
let start = self.len as usize;
let end = start + bytes.len();
assert!(
end <= 32,
"InstrBytes overflow: {} + {} exceeds 32-byte capacity",
start,
bytes.len()
);
self.data[start..end].copy_from_slice(bytes);
self.len = end as u8;
}
#[inline]
pub fn insert(&mut self, pos: usize, byte: u8) {
let len = self.len as usize;
assert!(
pos <= len && len < 32,
"InstrBytes insert: pos={} len={} out of bounds",
pos,
len
);
let mut i = len;
while i > pos {
self.data[i] = self.data[i - 1];
i -= 1;
}
self.data[pos] = byte;
self.len += 1;
}
#[inline]
pub fn remove(&mut self, pos: usize) {
let len = self.len as usize;
assert!(
pos < len,
"InstrBytes remove: pos={} out of bounds (len={})",
pos,
len
);
let mut i = pos;
while i + 1 < len {
self.data[i] = self.data[i + 1];
i += 1;
}
self.data[len - 1] = 0;
self.len -= 1;
}
#[inline]
pub fn len(&self) -> usize {
self.len as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn to_vec(&self) -> Vec<u8> {
self.as_ref().to_vec()
}
}
impl Default for InstrBytes {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl core::ops::Deref for InstrBytes {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
&self.data[..self.len as usize]
}
}
impl core::ops::DerefMut for InstrBytes {
#[inline]
fn deref_mut(&mut self) -> &mut [u8] {
&mut self.data[..self.len as usize]
}
}
impl AsRef<[u8]> for InstrBytes {
#[inline]
fn as_ref(&self) -> &[u8] {
self
}
}
impl AsMut<[u8]> for InstrBytes {
#[inline]
fn as_mut(&mut self) -> &mut [u8] {
self
}
}
impl core::fmt::Debug for InstrBytes {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl PartialEq for InstrBytes {
fn eq(&self, other: &Self) -> bool {
**self == **other
}
}
impl Eq for InstrBytes {}
impl PartialEq<[u8]> for InstrBytes {
fn eq(&self, other: &[u8]) -> bool {
**self == *other
}
}
impl PartialEq<Vec<u8>> for InstrBytes {
fn eq(&self, other: &Vec<u8>) -> bool {
**self == **other
}
}
#[derive(Debug, Clone)]
pub struct EncodedInstr {
pub bytes: InstrBytes,
pub relocation: Option<Relocation>,
pub relax: Option<RelaxInfo>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum RelocKind {
X86Relative,
Absolute,
#[cfg(feature = "arm")]
ArmBranch24,
#[cfg(feature = "arm")]
ArmLdrLit,
#[cfg(feature = "arm")]
ArmAdr,
#[cfg(feature = "arm")]
ThumbBranch8,
#[cfg(feature = "arm")]
ThumbBranch11,
#[cfg(feature = "arm")]
ThumbBl,
#[cfg(feature = "arm")]
ThumbBranchW,
#[cfg(feature = "arm")]
ThumbCondBranchW,
#[cfg(feature = "arm")]
ThumbLdrLit8,
#[cfg(feature = "aarch64")]
Aarch64Jump26,
#[cfg(feature = "aarch64")]
Aarch64Branch19,
#[cfg(feature = "aarch64")]
Aarch64Branch14,
#[cfg(feature = "aarch64")]
Aarch64LdrLit19,
#[cfg(feature = "aarch64")]
Aarch64Adr21,
#[cfg(feature = "aarch64")]
Aarch64Adrp,
#[cfg(feature = "aarch64")]
Aarch64AdrpAddPair,
#[cfg(feature = "riscv")]
RvJal20,
#[cfg(feature = "riscv")]
RvBranch12,
#[cfg(feature = "riscv")]
RvAuipc20,
#[cfg(feature = "riscv")]
RvCBranch8,
#[cfg(feature = "riscv")]
RvCJump11,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Relocation {
pub offset: usize,
pub size: u8,
pub label: alloc::rc::Rc<str>,
pub kind: RelocKind,
pub addend: i64,
pub trailing_bytes: u8,
}
#[derive(Debug, Clone)]
pub struct RelaxInfo {
pub short_bytes: InstrBytes,
pub short_reloc_offset: usize,
pub short_relocation: Option<Relocation>,
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64", feature = "riscv"))]
pub(crate) fn extract_label(op: &Operand) -> Option<(&str, i64)> {
match op {
Operand::Label(name) => Some((name.as_str(), 0)),
Operand::Expression(expr) => expr.label_addend(),
_ => None,
}
}
#[inline]
pub fn encode_instruction(instr: &Instruction, arch: Arch) -> Result<EncodedInstr, AsmError> {
#[allow(unreachable_patterns)]
match arch {
#[cfg(feature = "x86_64")]
Arch::X86_64 => encode_x86_64(instr),
#[cfg(feature = "x86")]
Arch::X86 => encode_x86_32(instr),
#[cfg(feature = "arm")]
Arch::Arm | Arch::Thumb => crate::arm::encode_arm(instr, arch),
#[cfg(feature = "aarch64")]
Arch::Aarch64 => crate::aarch64::encode_aarch64(instr),
#[cfg(feature = "riscv")]
Arch::Rv32 | Arch::Rv64 => crate::riscv::encode_riscv(instr, arch),
_ => Err(AsmError::Syntax {
msg: alloc::format!(
"encoder not implemented for {} (enable the feature flag)",
arch
),
span: instr.span,
}),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_x86_prefixes(buf: &mut InstrBytes, instr: &Instruction, ops: &OperandList) -> usize {
for pfx in &instr.prefixes {
match pfx {
Prefix::Lock => buf.push(0xF0),
Prefix::Rep => buf.push(0xF3),
Prefix::Repne => buf.push(0xF2),
Prefix::SegFs => buf.push(0x64),
Prefix::SegGs => buf.push(0x65),
}
}
let has_seg_prefix = instr
.prefixes
.iter()
.any(|p| matches!(p, Prefix::SegFs | Prefix::SegGs));
if !has_seg_prefix {
for op in ops {
if let Operand::Memory(mem) = op {
if let Some(seg) = mem.segment {
let seg_byte = match seg {
Register::Cs => Some(0x2E_u8),
Register::Ds => Some(0x3E),
Register::Es => Some(0x26),
Register::Fs => Some(0x64),
Register::Gs => Some(0x65),
Register::Ss => Some(0x36),
_ => None,
};
if let Some(b) = seg_byte {
buf.push(b);
}
}
}
}
}
buf.len()
}
#[cfg(feature = "x86_64")]
fn needs_addr_size_override(ops: &OperandList) -> bool {
for op in ops {
if let Operand::Memory(mem) = op {
if let Some(base) = mem.base {
if base == Register::Rip {
continue;
}
if base.size_bits() == 32 {
return true;
}
}
if let Some(idx) = mem.index {
if idx.size_bits() == 32 {
return true;
}
}
}
}
false
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn validate_lock_prefix(instr: &Instruction, ops: &OperandList) -> Result<(), AsmError> {
if instr.prefixes.contains(&Prefix::Lock) {
let has_memory_dst = matches!(ops.first(), Some(Operand::Memory(_)));
if !has_memory_dst {
return Err(AsmError::InvalidOperands {
detail: String::from("LOCK prefix requires a memory destination operand"),
span: instr.span,
});
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn propagate_disp_label_reloc(
buf: &[u8],
ops: &OperandList,
prefix_len: usize,
reloc: &mut Option<Relocation>,
) {
if reloc.is_some() {
return;
}
for op in ops {
if let Operand::Memory(mem) = op {
if let Some(ref label) = mem.disp_label {
if let Some(off) = find_disp_offset_structural(buf, prefix_len) {
let kind = if mem.base == Some(Register::Rip) {
RelocKind::X86Relative
} else {
RelocKind::Absolute
};
*reloc = Some(Relocation {
offset: off,
size: 4,
label: alloc::rc::Rc::from(&**label),
kind,
addend: mem.disp,
trailing_bytes: 0,
});
}
break; }
}
}
if let Some(ref mut r) = reloc {
if r.kind == RelocKind::X86Relative {
let end_of_reloc = r.offset + r.size as usize;
r.trailing_bytes = (buf.len() - end_of_reloc) as u8;
}
}
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn is_post_prefix_legacy_byte(b: u8) -> bool {
matches!(b, 0x66 | 0x67 | 0xF2 | 0xF3)
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn find_disp_offset_structural(buf: &[u8], prefix_len: usize) -> Option<usize> {
if buf.len() <= prefix_len {
return None;
}
let mut pos = prefix_len;
while pos < buf.len() && is_post_prefix_legacy_byte(buf[pos]) {
pos += 1;
}
if pos >= buf.len() {
return None;
}
let modrm_pos = if buf[pos] == 0xC5 {
pos + 3 } else if buf[pos] == 0xC4 {
pos + 4 } else if buf[pos] == 0x62 {
pos + 5 } else {
if (buf[pos] & 0xF0) == 0x40 {
pos += 1;
}
if pos >= buf.len() {
return None;
}
if buf[pos] == 0x0F {
pos += 1;
if pos >= buf.len() {
return None;
}
if buf[pos] == 0x38 || buf[pos] == 0x3A {
pos += 2; } else {
pos += 1; }
} else {
pos += 1; }
pos
};
if modrm_pos >= buf.len() {
return None;
}
let modrm = buf[modrm_pos];
let mod_bits = (modrm >> 6) & 0x03;
let rm = modrm & 0x07;
if mod_bits == 0x03 {
return None;
}
let mut disp_pos = modrm_pos + 1;
if rm == 0x04 {
if disp_pos >= buf.len() {
return None;
}
let sib_base = buf[disp_pos] & 0x07;
disp_pos += 1;
match mod_bits {
0b00 if sib_base == 0x05 => Some(disp_pos), 0b00 => None, 0b01 | 0b10 => Some(disp_pos), _ => None,
}
} else {
match mod_bits {
0b00 if rm == 0x05 => Some(disp_pos), 0b00 => None, 0b01 | 0b10 => Some(disp_pos), _ => None,
}
}
}
#[cfg(feature = "x86_64")]
fn fixup_rip_trailing_bytes(buf: &[u8], reloc: &mut Option<Relocation>) {
if let Some(ref mut r) = reloc {
if r.kind == RelocKind::X86Relative {
let end_of_reloc = r.offset + r.size as usize;
r.trailing_bytes = (buf.len() - end_of_reloc) as u8;
}
}
}
#[cfg(feature = "x86_64")]
fn encode_x86_64(instr: &Instruction) -> Result<EncodedInstr, AsmError> {
let mut buf = InstrBytes::new();
let mut reloc: Option<Relocation> = None;
let mut relax_info: Option<RelaxInfo> = None;
let ops = &instr.operands;
let prefix_len = emit_x86_prefixes(&mut buf, instr, ops);
if needs_addr_size_override(ops) {
buf.push(0x67);
}
let mnemonic = instr.mnemonic.as_str();
match crate::x86::dispatch_x86_64(mnemonic, &mut buf, ops, instr, &mut reloc, &mut relax_info) {
Some(Ok(())) => {}
Some(Err(e)) => return Err(e),
None => {
return Err(AsmError::UnknownMnemonic {
mnemonic: String::from(mnemonic),
arch: crate::error::ArchName::X86_64,
span: instr.span,
});
}
}
validate_lock_prefix(instr, ops)?;
propagate_disp_label_reloc(&buf, ops, prefix_len, &mut reloc);
fixup_rip_trailing_bytes(&buf, &mut reloc);
Ok(EncodedInstr {
bytes: buf,
relocation: reloc,
relax: relax_info,
})
}
#[cfg(feature = "x86")]
fn validate_x86_32(instr: &Instruction) -> Result<(), AsmError> {
for op in &instr.operands {
match op {
Operand::Register(reg) => {
if reg.size_bits() == 64 {
return Err(AsmError::InvalidOperands {
detail: String::from("64-bit registers are not available in 32-bit mode"),
span: instr.span,
});
}
if reg.is_extended() {
return Err(AsmError::InvalidOperands {
detail: String::from(
"extended registers (R8-R15) are not available in 32-bit mode",
),
span: instr.span,
});
}
if reg.requires_rex_for_byte() {
return Err(AsmError::InvalidOperands {
detail: String::from(
"SPL/BPL/SIL/DIL are not available in 32-bit mode (require REX)",
),
span: instr.span,
});
}
if matches!(reg, Register::Rip) {
return Err(AsmError::InvalidOperands {
detail: String::from(
"RIP-relative addressing is not available in 32-bit mode",
),
span: instr.span,
});
}
}
Operand::Memory(mem) => {
if let Some(base) = mem.base {
if base == Register::Rip {
return Err(AsmError::InvalidOperands {
detail: String::from(
"RIP-relative addressing is not available in 32-bit mode",
),
span: instr.span,
});
}
if base.size_bits() == 64 || base.is_extended() {
return Err(AsmError::InvalidOperands {
detail: String::from(
"64-bit/extended registers cannot be used as memory base in 32-bit mode",
),
span: instr.span,
});
}
}
if let Some(idx) = mem.index {
if idx.size_bits() == 64 || idx.is_extended() {
return Err(AsmError::InvalidOperands {
detail: String::from(
"64-bit/extended registers cannot be used as memory index in 32-bit mode",
),
span: instr.span,
});
}
}
}
_ => {}
}
}
if instr.mnemonic == "movsxd" {
return Err(AsmError::UnknownMnemonic {
mnemonic: String::from("movsxd"),
arch: crate::error::ArchName::X86,
span: instr.span,
});
}
Ok(())
}
#[cfg(feature = "x86")]
fn encode_x86_32(instr: &Instruction) -> Result<EncodedInstr, AsmError> {
validate_x86_32(instr)?;
let mut buf = InstrBytes::new();
let mut reloc: Option<Relocation> = None;
let mut relax_info: Option<RelaxInfo> = None;
let ops = &instr.operands;
let prefix_len = emit_x86_prefixes(&mut buf, instr, ops);
let mnemonic = instr.mnemonic.as_str();
match mnemonic {
"push" => {
encode_push_32(&mut buf, ops, instr, &mut reloc)?;
return Ok(EncodedInstr {
bytes: buf,
relocation: reloc,
relax: relax_info,
});
}
"pop" => {
encode_pop_32(&mut buf, ops, instr)?;
return Ok(EncodedInstr {
bytes: buf,
relocation: reloc,
relax: relax_info,
});
}
"inc" | "dec" => {
if let [Operand::Register(reg)] = ops.as_slice() {
let size = reg_size(*reg);
if size == 16 || size == 32 {
if size == 16 {
buf.push(0x66);
}
let base = if mnemonic == "inc" { 0x40 } else { 0x48 };
buf.push(base + reg.base_code());
return Ok(EncodedInstr {
bytes: buf,
relocation: reloc,
relax: relax_info,
});
}
}
}
_ => {}
}
match crate::x86::dispatch_x86_64(mnemonic, &mut buf, ops, instr, &mut reloc, &mut relax_info) {
Some(Ok(())) => {}
Some(Err(e)) => return Err(e),
None => {
return Err(AsmError::UnknownMnemonic {
mnemonic: String::from(mnemonic),
arch: crate::error::ArchName::X86,
span: instr.span,
});
}
}
validate_lock_prefix(instr, ops)?;
propagate_disp_label_reloc(&buf, ops, prefix_len, &mut reloc);
Ok(EncodedInstr {
bytes: buf,
relocation: reloc,
relax: relax_info,
})
}
#[cfg(feature = "x86")]
pub fn encode_instruction_16(instr: &Instruction) -> Result<EncodedInstr, AsmError> {
let mut result = encode_x86_32(instr)?;
toggle_operand_size_prefix_16(&mut result.bytes, &instr.operands, &mut result.relocation);
Ok(result)
}
#[cfg(feature = "x86")]
fn toggle_operand_size_prefix_16(
buf: &mut InstrBytes,
ops: &OperandList,
reloc: &mut Option<Relocation>,
) {
let mut found_66_at = None;
for i in 0..buf.len() {
let b = buf[i];
if b == 0x66 {
found_66_at = Some(i);
break;
}
if !is_legacy_prefix(b) && b != 0x67 {
break;
}
}
if let Some(pos) = found_66_at {
buf.remove(pos);
if let Some(ref mut r) = reloc {
if r.offset > pos {
r.offset -= 1;
}
}
} else {
let has_32bit_gpr = ops.iter().any(|op| {
if let Operand::Register(r) = op {
r.size_bits() == 32 && !matches!(r, Register::Eip)
} else {
false
}
});
let has_dword_mem = ops.iter().any(
|op| matches!(op, Operand::Memory(m) if m.size == Some(crate::ir::OperandSize::Dword)),
);
if has_32bit_gpr || has_dword_mem {
let mut insert_pos = 0;
for i in 0..buf.len() {
let b = buf[i];
if is_legacy_prefix(b) || b == 0x67 {
insert_pos = i + 1;
} else {
break;
}
}
buf.insert(insert_pos, 0x66);
if let Some(ref mut r) = reloc {
if r.offset >= insert_pos {
r.offset += 1;
}
}
}
}
}
#[cfg(feature = "x86")]
#[inline]
fn is_legacy_prefix(b: u8) -> bool {
matches!(
b,
0xF0 | 0xF2 | 0xF3 | 0x26 | 0x2E | 0x36 | 0x3E | 0x64 | 0x65 | 0x66 | 0x67
)
}
#[cfg(feature = "x86")]
fn encode_push_32(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands("push", "expected 1 operand", instr.span));
}
match &ops[0] {
Operand::Register(reg) => {
let size = reg.size_bits();
match reg {
Register::Es => {
buf.push(0x06);
return Ok(());
}
Register::Cs => {
buf.push(0x0E);
return Ok(());
}
Register::Ss => {
buf.push(0x16);
return Ok(());
}
Register::Ds => {
buf.push(0x1E);
return Ok(());
}
Register::Fs => {
buf.push(0x0F);
buf.push(0xA0);
return Ok(());
}
Register::Gs => {
buf.push(0x0F);
buf.push(0xA8);
return Ok(());
}
_ => {}
}
if size == 8 {
return Err(invalid_operands(
"push",
"push does not accept 8-bit registers",
instr.span,
));
}
if size == 16 {
buf.push(0x66); }
buf.push(0x50 + reg.base_code());
}
Operand::Immediate(imm) => {
if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
buf.push(0x6A);
buf.push(*imm as i8 as u8);
} else if *imm >= i32::MIN as i128 && *imm <= u32::MAX as i128 {
buf.push(0x68);
buf.extend_from_slice(&(*imm as i32).to_le_bytes());
} else {
return Err(invalid_operands(
"push",
"immediate value out of range for push (must fit in 32 bits)",
instr.span,
));
}
}
Operand::Memory(mem) => {
let msize = mem.size.map(|s| s.bits()).unwrap_or(32);
if msize == 16 {
buf.push(0x66);
}
buf.push(0xFF);
emit_mem_modrm(buf, 6, mem);
}
op @ (Operand::Label(_) | Operand::Expression(_)) => {
let Some((label, addend)) = extract_label(op) else {
return Err(invalid_operands("push", "unsupported operand", instr.span));
};
buf.push(0x68);
let reloc_off = buf.len();
buf.extend_from_slice(&0i32.to_le_bytes());
*reloc = Some(Relocation {
offset: reloc_off,
size: 4,
label: alloc::rc::Rc::from(label),
kind: RelocKind::Absolute,
addend,
trailing_bytes: 0,
});
}
_ => return Err(invalid_operands("push", "unsupported operand", instr.span)),
}
Ok(())
}
#[cfg(feature = "x86")]
fn encode_pop_32(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands("pop", "expected 1 operand", instr.span));
}
match &ops[0] {
Operand::Register(reg) => {
let size = reg.size_bits();
match reg {
Register::Es => {
buf.push(0x07);
return Ok(());
}
Register::Ss => {
buf.push(0x17);
return Ok(());
}
Register::Ds => {
buf.push(0x1F);
return Ok(());
}
Register::Fs => {
buf.push(0x0F);
buf.push(0xA1);
return Ok(());
}
Register::Gs => {
buf.push(0x0F);
buf.push(0xA9);
return Ok(());
}
Register::Cs => {
return Err(invalid_operands("pop", "cannot pop into CS", instr.span));
}
_ => {}
}
if size == 8 {
return Err(invalid_operands(
"pop",
"pop does not accept 8-bit registers",
instr.span,
));
}
if size == 16 {
buf.push(0x66);
}
buf.push(0x58 + reg.base_code());
}
Operand::Memory(mem) => {
let msize = mem.size.map(|s| s.bits()).unwrap_or(32);
if msize == 16 {
buf.push(0x66);
}
buf.push(0x8F);
emit_mem_modrm(buf, 0, mem);
}
_ => return Err(invalid_operands("pop", "unsupported operand", instr.span)),
}
Ok(())
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn rex(w: bool, r: bool, x: bool, b: bool) -> u8 {
let mut val: u8 = 0x40;
if w {
val |= 0x08;
}
if r {
val |= 0x04;
}
if x {
val |= 0x02;
}
if b {
val |= 0x01;
}
val
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn needs_rex(w: bool, r: bool, x: bool, b: bool) -> bool {
w || r || x || b
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn modrm(mod_: u8, reg: u8, rm: u8) -> u8 {
(mod_ << 6) | ((reg & 7) << 3) | (rm & 7)
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn sib(scale: u8, index: u8, base: u8) -> u8 {
let ss = match scale {
1 => 0,
2 => 1,
4 => 2,
8 => 3,
_ => 0,
};
(ss << 6) | ((index & 7) << 3) | (base & 7)
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn reg_size(reg: Register) -> u8 {
let s = reg.size_bits();
debug_assert!(s <= 128, "reg_size() used on vector register wider than u8");
s as u8
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn check_high_byte_rex_conflict(
regs: &[Register],
span: crate::error::Span,
) -> Result<(), AsmError> {
let has_high = regs.iter().any(|r| r.is_high_byte());
let needs_rex = regs
.iter()
.any(|r| r.is_extended() || r.requires_rex_for_byte() || r.size_bits() == 64);
if has_high && needs_rex {
return Err(AsmError::InvalidOperands {
detail: String::from(
"high-byte registers (AH, BH, CH, DH) cannot be used with REX-requiring operands (64-bit regs, extended regs R8-R15, SPL/BPL/SIL/DIL)"
),
span,
});
}
Ok(())
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_rr(
buf: &mut InstrBytes,
opcode: &[u8],
dst: Register,
src: Register,
span: crate::error::Span,
) -> Result<(), AsmError> {
let size = reg_size(dst);
let w = size == 64;
let r = src.is_extended();
let b = dst.is_extended();
if size == 8 {
check_high_byte_rex_conflict(&[dst, src], span)?;
}
if size == 16 {
buf.push(0x66);
}
let need_rex =
needs_rex(w, r, false, b) || dst.requires_rex_for_byte() || src.requires_rex_for_byte();
if need_rex {
buf.push(rex(w, r, false, b));
}
buf.extend_from_slice(opcode);
buf.push(modrm(0b11, src.base_code(), dst.base_code()));
Ok(())
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn set_mem_reloc(
reloc: &mut Option<Relocation>,
mem: &MemoryOperand,
disp_offset: Option<usize>,
buf_len: usize,
) {
if let Some(ref label) = mem.disp_label {
*reloc = Some(Relocation {
offset: disp_offset.unwrap_or(buf_len),
size: 4,
label: alloc::rc::Rc::from(&**label),
kind: if mem.base == Some(Register::Rip) {
RelocKind::X86Relative
} else {
RelocKind::Absolute
},
addend: mem.disp,
trailing_bytes: 0, });
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn emit_mem_modrm(
buf: &mut InstrBytes,
reg_field: u8,
mem: &MemoryOperand,
) -> Option<usize> {
let base = mem.base;
let index = mem.index;
let disp = mem.disp;
if base == Some(Register::Rip) && index.is_none() {
buf.push(modrm(0b00, reg_field, 0b101));
let reloc_offset = buf.len();
buf.extend_from_slice(&(disp as i32).to_le_bytes());
return Some(reloc_offset);
}
if base.is_none() && index.is_none() {
buf.push(modrm(0b00, reg_field, 0b100));
buf.push(sib(1, 0b100, 0b101));
let reloc_offset = buf.len();
buf.extend_from_slice(&(disp as i32).to_le_bytes());
return Some(reloc_offset);
}
if let (None, Some(idx_reg)) = (base, index) {
buf.push(modrm(0b00, reg_field, 0b100));
buf.push(sib(mem.scale, idx_reg.base_code(), 0b101));
let reloc_offset = buf.len();
buf.extend_from_slice(&(disp as i32).to_le_bytes());
return Some(reloc_offset);
}
let base = base?;
let need_sib = index.is_some() || base.base_code() == 4;
let (mod_bits, disp_size) = if disp == 0 && base.base_code() != 5 {
(0b00, 0)
} else if (-128..=127).contains(&disp) {
(0b01, 1)
} else {
(0b10, 4)
};
if need_sib {
let idx_reg = index.unwrap_or(Register::Rsp);
buf.push(modrm(mod_bits, reg_field, 0b100));
buf.push(sib(mem.scale, idx_reg.base_code(), base.base_code()));
} else {
buf.push(modrm(mod_bits, reg_field, base.base_code()));
}
let reloc_offset = if disp_size > 0 { Some(buf.len()) } else { None };
match disp_size {
1 => buf.push(disp as i8 as u8),
4 => buf.extend_from_slice(&(disp as i32).to_le_bytes()),
_ => {}
}
reloc_offset
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn emit_rex_for_reg_mem(
buf: &mut InstrBytes,
reg: Register,
mem: &MemoryOperand,
) -> Result<(), AsmError> {
let w = reg.size_bits() == 64;
let r = reg.is_extended();
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if reg.size_bits() == 16 {
buf.push(0x66);
}
if reg.size_bits() == 8 && reg.is_high_byte() {
let mem_needs_rex = x || b;
if mem_needs_rex {
return Err(AsmError::InvalidOperands {
detail: String::from(
"high-byte registers (AH, BH, CH, DH) cannot be used with memory operands requiring REX prefix (R8-R15 base/index)"
),
span: crate::error::Span { line: 0, col: 0, offset: 0, len: 0 },
});
}
}
let need = needs_rex(w, r, x, b) || reg.requires_rex_for_byte();
if need {
buf.push(rex(w, r, x, b));
}
Ok(())
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn emit_rex_for_digit_mem(buf: &mut InstrBytes, size: u8, mem: &MemoryOperand) {
let w = size == 64;
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, x, b) {
buf.push(rex(w, false, x, b));
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_nop(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.is_empty() {
buf.push(0x90);
Ok(())
} else {
Err(AsmError::InvalidOperands {
detail: String::from("nop takes no operands"),
span: instr.span,
})
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_multibyte_nop(buf: &mut InstrBytes, mnemonic: &str) -> Result<(), AsmError> {
let n: usize = mnemonic[3..].parse().unwrap_or(1);
match n {
2 => buf.extend_from_slice(&[0x66, 0x90]),
3 => buf.extend_from_slice(&[0x0F, 0x1F, 0x00]),
4 => buf.extend_from_slice(&[0x0F, 0x1F, 0x40, 0x00]),
5 => buf.extend_from_slice(&[0x0F, 0x1F, 0x44, 0x00, 0x00]),
6 => buf.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00]),
7 => buf.extend_from_slice(&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]),
8 => buf.extend_from_slice(&[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]),
9 => buf.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]),
_ => buf.push(0x90),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_int(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands(
"int",
"expected one immediate operand",
instr.span,
));
}
match &ops[0] {
Operand::Immediate(3) => buf.push(0xCC), Operand::Immediate(n) if *n >= 0 && *n <= 255 => {
buf.push(0xCD);
buf.push(*n as u8);
}
_ => {
return Err(invalid_operands(
"int",
"expected immediate 0-255",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_ret(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.is_empty() {
buf.push(0xC3); } else if ops.len() == 1 {
match &ops[0] {
Operand::Immediate(n) if *n >= 0 && *n <= 65535 => {
buf.push(0xC2); buf.extend_from_slice(&(*n as u16).to_le_bytes());
}
_ => {
return Err(invalid_operands(
"ret",
"expected immediate 0-65535",
instr.span,
))
}
}
} else {
return Err(invalid_operands(
"ret",
"expected 0 or 1 operands",
instr.span,
));
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_retf(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.is_empty() {
buf.push(0xCB); } else if ops.len() == 1 {
match &ops[0] {
Operand::Immediate(n) if *n >= 0 && *n <= 65535 => {
buf.push(0xCA); buf.extend_from_slice(&(*n as u16).to_le_bytes());
}
_ => {
return Err(invalid_operands(
"retf",
"expected immediate 0-65535",
instr.span,
))
}
}
} else {
return Err(invalid_operands(
"retf",
"expected 0 or 1 operands",
instr.span,
));
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_mov(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands("mov", "expected 2 operands", instr.span));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
if size != reg_size(*src) {
return Err(invalid_operands("mov", "operand size mismatch", instr.span));
}
let opcode = if size == 8 {
&[0x88u8] as &[u8]
} else {
&[0x89u8]
};
emit_rr(buf, opcode, *dst, *src, instr.span)?;
}
(Operand::Register(dst), Operand::Immediate(imm)) => {
encode_mov_reg_imm(buf, *dst, *imm, instr.span)?;
}
(Operand::Register(dst), op @ (Operand::Label(_) | Operand::Expression(_))) => {
let Some((label, addend)) = extract_label(op) else {
return Err(invalid_operands(
"mov",
"expected label expression",
instr.span,
));
};
let size = reg_size(*dst);
if size == 64 {
let w = true;
let b = dst.is_extended();
buf.push(rex(w, false, false, b));
buf.push(0xB8 + dst.base_code());
let reloc_off = buf.len();
buf.extend_from_slice(&0u64.to_le_bytes());
*reloc = Some(Relocation {
offset: reloc_off,
size: 8,
label: alloc::rc::Rc::from(label),
kind: RelocKind::Absolute,
addend,
trailing_bytes: 0,
});
} else {
return Err(invalid_operands(
"mov",
"label operand requires 64-bit register",
instr.span,
));
}
}
(Operand::Register(dst), Operand::Memory(mem)) => {
let size = reg_size(*dst);
let opcode: u8 = if size == 8 { 0x8A } else { 0x8B };
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(opcode);
let reloc_off = emit_mem_modrm(buf, dst.base_code(), mem);
if let Some(ref label) = mem.disp_label {
*reloc = Some(Relocation {
offset: reloc_off.unwrap_or(buf.len()),
size: 4,
label: alloc::rc::Rc::from(&**label),
kind: if mem.base == Some(Register::Rip) {
RelocKind::X86Relative
} else {
RelocKind::Absolute
},
addend: mem.disp,
trailing_bytes: 0,
});
}
}
(Operand::Memory(mem), Operand::Register(src)) => {
let size = reg_size(*src);
let opcode: u8 = if size == 8 { 0x88 } else { 0x89 };
emit_rex_for_reg_mem(buf, *src, mem)?;
buf.push(opcode);
let disp_off = emit_mem_modrm(buf, src.base_code(), mem);
set_mem_reloc(reloc, mem, disp_off, buf.len());
}
(Operand::Memory(mem), Operand::Immediate(imm)) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
if size == 64 {
let v = *imm;
if v > i64::from(i32::MAX) as i128 || v < i64::from(i32::MIN) as i128 {
return Err(invalid_operands(
"mov",
"immediate too large for mov [mem], imm (max sign-extended imm32); use mov reg, imm64 + mov [mem], reg",
instr.span,
));
}
}
let opcode: u8 = if size == 8 { 0xC6 } else { 0xC7 };
emit_rex_for_digit_mem(buf, size, mem);
buf.push(opcode);
let disp_off = emit_mem_modrm(buf, 0, mem); set_mem_reloc(reloc, mem, disp_off, buf.len());
emit_imm(buf, *imm, if size > 32 { 32 } else { size }); }
_ => {
return Err(invalid_operands(
"mov",
"unsupported operand combination",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_mov_reg_imm(
buf: &mut InstrBytes,
dst: Register,
imm: i128,
span: Span,
) -> Result<(), AsmError> {
let size = reg_size(dst);
match size {
8 => {
let b = dst.is_extended();
let need = b || dst.requires_rex_for_byte();
if need {
buf.push(rex(false, false, false, b));
}
buf.push(0xB0 + dst.base_code());
buf.push(imm as u8);
}
16 => {
buf.push(0x66);
let b = dst.is_extended();
if b {
buf.push(rex(false, false, false, b));
}
buf.push(0xB8 + dst.base_code());
buf.extend_from_slice(&(imm as u16).to_le_bytes());
}
32 => {
let b = dst.is_extended();
if b {
buf.push(rex(false, false, false, b));
}
buf.push(0xB8 + dst.base_code());
buf.extend_from_slice(&(imm as u32).to_le_bytes());
}
64 => {
let b = dst.is_extended();
if imm >= 0 && imm <= u32::MAX as i128 {
if b {
buf.push(rex(false, false, false, true));
}
buf.push(0xB8 + dst.base_code());
buf.extend_from_slice(&(imm as u32).to_le_bytes());
} else if imm >= i32::MIN as i128 && imm <= i32::MAX as i128 {
buf.push(rex(true, false, false, b));
buf.push(0xC7);
buf.push(modrm(0b11, 0, dst.base_code()));
buf.extend_from_slice(&(imm as i32).to_le_bytes());
} else {
buf.push(rex(true, false, false, b));
buf.push(0xB8 + dst.base_code());
buf.extend_from_slice(&(imm as u64).to_le_bytes());
}
}
_ => {
return Err(AsmError::InvalidOperands {
detail: String::from("unsupported register size for mov immediate"),
span,
});
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_lea(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands("lea", "expected 2 operands", instr.span));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Memory(mem)) => {
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(0x8D);
let reloc_off = emit_mem_modrm(buf, dst.base_code(), mem);
if let Some(ref label) = mem.disp_label {
*reloc = Some(Relocation {
offset: reloc_off.unwrap_or(buf.len()),
size: 4,
label: alloc::rc::Rc::from(&**label),
kind: if mem.base == Some(Register::Rip) {
RelocKind::X86Relative
} else {
RelocKind::Absolute
},
addend: mem.disp,
trailing_bytes: 0,
});
}
}
_ => return Err(invalid_operands("lea", "expected reg, [mem]", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_push(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands("push", "expected 1 operand", instr.span));
}
match &ops[0] {
Operand::Register(reg) => {
let size = reg.size_bits();
match reg {
Register::Fs => {
buf.push(0x0F);
buf.push(0xA0);
return Ok(());
}
Register::Gs => {
buf.push(0x0F);
buf.push(0xA8);
return Ok(());
}
Register::Cs | Register::Ds | Register::Es | Register::Ss => {
return Err(invalid_operands(
"push",
"CS/DS/ES/SS push not valid in 64-bit mode",
instr.span,
));
}
_ => {}
}
if size == 8 || size == 32 {
return Err(invalid_operands(
"push",
"push requires 64-bit or 16-bit register in 64-bit mode",
instr.span,
));
}
let b = reg.is_extended();
if size == 16 {
buf.push(0x66);
}
if b {
buf.push(rex(false, false, false, true));
}
buf.push(0x50 + reg.base_code());
}
Operand::Immediate(imm) => {
if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
buf.push(0x6A);
buf.push(*imm as i8 as u8);
} else if *imm >= i32::MIN as i128 && *imm <= u32::MAX as i128 {
buf.push(0x68);
buf.extend_from_slice(&(*imm as i32).to_le_bytes());
} else {
return Err(invalid_operands(
"push",
"immediate value out of range for push (must fit in 32 bits)",
instr.span,
));
}
}
Operand::Memory(mem) => {
emit_rex_for_digit_mem(buf, 0, mem);
buf.push(0xFF);
emit_mem_modrm(buf, 6, mem); }
op @ (Operand::Label(_) | Operand::Expression(_)) => {
let Some((label, addend)) = extract_label(op) else {
return Err(invalid_operands("push", "unsupported operand", instr.span));
};
buf.push(0x68);
let reloc_off = buf.len();
buf.extend_from_slice(&0i32.to_le_bytes());
*reloc = Some(Relocation {
offset: reloc_off,
size: 4,
label: alloc::rc::Rc::from(label),
kind: RelocKind::Absolute,
addend,
trailing_bytes: 0,
});
}
_ => return Err(invalid_operands("push", "unsupported operand", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_pop(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands("pop", "expected 1 operand", instr.span));
}
match &ops[0] {
Operand::Register(reg) => {
let size = reg.size_bits();
match reg {
Register::Fs => {
buf.push(0x0F);
buf.push(0xA1);
return Ok(());
}
Register::Gs => {
buf.push(0x0F);
buf.push(0xA9);
return Ok(());
}
Register::Cs | Register::Ds | Register::Es | Register::Ss => {
return Err(invalid_operands(
"pop",
"CS/DS/ES/SS pop not valid in 64-bit mode",
instr.span,
));
}
_ => {}
}
if size == 8 || size == 32 {
return Err(invalid_operands(
"pop",
"pop requires 64-bit or 16-bit register in 64-bit mode",
instr.span,
));
}
let b = reg.is_extended();
if size == 16 {
buf.push(0x66);
}
if b {
buf.push(rex(false, false, false, true));
}
buf.push(0x58 + reg.base_code());
}
Operand::Memory(mem) => {
emit_rex_for_digit_mem(buf, 0, mem);
buf.push(0x8F);
emit_mem_modrm(buf, 0, mem);
}
_ => return Err(invalid_operands("pop", "unsupported operand", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_alu(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
alu_num: u8,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 2 operands",
instr.span,
));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
let base_opcode = if size == 8 {
alu_num * 8
} else {
alu_num * 8 + 1
};
emit_rr(buf, &[base_opcode], *dst, *src, instr.span)?;
}
(Operand::Register(dst), Operand::Immediate(imm)) => {
encode_alu_reg_imm(buf, *dst, *imm, alu_num)?;
}
(Operand::Register(dst), Operand::Memory(mem)) => {
let size = reg_size(*dst);
let opcode: u8 = if size == 8 {
alu_num * 8 + 2
} else {
alu_num * 8 + 3
};
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(opcode);
let disp_off = emit_mem_modrm(buf, dst.base_code(), mem);
set_mem_reloc(reloc, mem, disp_off, buf.len());
}
(Operand::Memory(mem), Operand::Register(src)) => {
let size = reg_size(*src);
let opcode: u8 = if size == 8 {
alu_num * 8
} else {
alu_num * 8 + 1
};
emit_rex_for_reg_mem(buf, *src, mem)?;
buf.push(opcode);
let disp_off = emit_mem_modrm(buf, src.base_code(), mem);
set_mem_reloc(reloc, mem, disp_off, buf.len());
}
(Operand::Memory(mem), Operand::Immediate(imm)) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
let disp_off = encode_alu_mem_imm(buf, mem, *imm, alu_num, size)?;
set_mem_reloc(reloc, mem, disp_off, buf.len());
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"unsupported operand combination",
instr.span,
));
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_alu_reg_imm(
buf: &mut InstrBytes,
dst: Register,
imm: i128,
alu_num: u8,
) -> Result<(), AsmError> {
let size = reg_size(dst);
if dst.base_code() == 0 && !dst.is_extended() && size == 8 {
buf.push(alu_num * 8 + 4);
buf.push(imm as u8);
return Ok(());
}
if size == 8 {
let b = dst.is_extended();
let need = b || dst.requires_rex_for_byte();
if need {
buf.push(rex(false, false, false, b));
}
buf.push(0x80);
buf.push(modrm(0b11, alu_num, dst.base_code()));
buf.push(imm as u8);
} else if imm >= i8::MIN as i128 && imm <= i8::MAX as i128 {
let w = size == 64;
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, false, b) {
buf.push(rex(w, false, false, b));
}
buf.push(0x83);
buf.push(modrm(0b11, alu_num, dst.base_code()));
buf.push(imm as i8 as u8);
} else {
let w = size == 64;
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, false, b) {
buf.push(rex(w, false, false, b));
}
if dst.base_code() == 0 && !dst.is_extended() {
buf.push(alu_num * 8 + 5);
} else {
buf.push(0x81);
buf.push(modrm(0b11, alu_num, dst.base_code()));
}
let imm_size = if size > 32 { 32 } else { size };
emit_imm(buf, imm, imm_size);
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_alu_mem_imm(
buf: &mut InstrBytes,
mem: &MemoryOperand,
imm: i128,
alu_num: u8,
size: u8,
) -> Result<Option<usize>, AsmError> {
if size == 8 {
emit_rex_for_digit_mem(buf, size, mem);
buf.push(0x80);
let disp_off = emit_mem_modrm(buf, alu_num, mem);
buf.push(imm as u8);
Ok(disp_off)
} else if imm >= i8::MIN as i128 && imm <= i8::MAX as i128 {
emit_rex_for_digit_mem(buf, size, mem);
buf.push(0x83);
let disp_off = emit_mem_modrm(buf, alu_num, mem);
buf.push(imm as i8 as u8);
Ok(disp_off)
} else {
emit_rex_for_digit_mem(buf, size, mem);
buf.push(0x81);
let disp_off = emit_mem_modrm(buf, alu_num, mem);
let imm_size = if size > 32 { 32 } else { size };
emit_imm(buf, imm, imm_size);
Ok(disp_off)
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_test(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands("test", "expected 2 operands", instr.span));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
let opcode = if size == 8 { 0x84u8 } else { 0x85u8 };
emit_rr(buf, &[opcode], *dst, *src, instr.span)?;
}
(Operand::Register(dst), Operand::Immediate(imm)) => {
let size = reg_size(*dst);
if dst.base_code() == 0 && !dst.is_extended() && size == 8 {
buf.push(0xA8);
buf.push(*imm as u8);
} else if dst.base_code() == 0 && !dst.is_extended() && size > 8 {
let w = size == 64;
if size == 16 {
buf.push(0x66);
}
if w {
buf.push(rex(true, false, false, false));
}
buf.push(0xA9);
let imm_size = if size > 32 { 32 } else { size };
emit_imm(buf, *imm, imm_size);
} else {
let w = size == 64;
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
if need {
buf.push(rex(w, false, false, b));
}
buf.push(if size == 8 { 0xF6 } else { 0xF7 });
buf.push(modrm(0b11, 0, dst.base_code()));
let imm_size = if size == 8 {
8
} else if size > 32 {
32
} else {
size
};
emit_imm(buf, *imm, imm_size);
}
}
(Operand::Memory(mem), Operand::Register(src)) => {
let size = reg_size(*src);
let opcode = if size == 8 { 0x84u8 } else { 0x85u8 };
emit_rex_for_reg_mem(buf, *src, mem)?;
buf.push(opcode);
let disp_off = emit_mem_modrm(buf, src.base_code(), mem);
set_mem_reloc(reloc, mem, disp_off, buf.len());
}
(Operand::Memory(mem), Operand::Immediate(imm)) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
let opcode = if size == 8 { 0xF6u8 } else { 0xF7u8 };
emit_rex_for_digit_mem(buf, size, mem);
buf.push(opcode);
let disp_off = emit_mem_modrm(buf, 0, mem); let imm_size = if size == 8 {
8
} else if size > 32 {
32
} else {
size
};
emit_imm(buf, *imm, imm_size);
set_mem_reloc(reloc, mem, disp_off, buf.len());
}
_ => {
return Err(invalid_operands(
"test",
"unsupported operand combination",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_unary(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
digit: u8,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 1 operand",
instr.span,
));
}
match &ops[0] {
Operand::Register(reg) => {
let size = reg_size(*reg);
let w = size == 64;
let b = reg.is_extended();
if size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, false, false, b) || reg.requires_rex_for_byte();
if need {
buf.push(rex(w, false, false, b));
}
buf.push(if size == 8 { 0xF6 } else { 0xF7 });
buf.push(modrm(0b11, digit, reg.base_code()));
}
Operand::Memory(mem) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
emit_rex_for_digit_mem(buf, size, mem);
buf.push(if size == 8 { 0xF6 } else { 0xF7 });
emit_mem_modrm(buf, digit, mem);
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected register or memory operand",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_imul(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match ops.len() {
1 => {
encode_unary(buf, ops, instr, 5)
}
2 => {
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
if size == 8 {
return Err(invalid_operands(
"imul",
"8-bit operands not supported for 2/3-operand IMUL",
instr.span,
));
}
let w = size == 64;
let r = dst.is_extended();
let b = src.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, false, b) {
buf.push(rex(w, r, false, b));
}
buf.push(0x0F);
buf.push(0xAF);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
}
(Operand::Register(dst), Operand::Memory(mem)) => {
if reg_size(*dst) == 8 {
return Err(invalid_operands(
"imul",
"8-bit operands not supported for 2/3-operand IMUL",
instr.span,
));
}
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(0x0F);
buf.push(0xAF);
emit_mem_modrm(buf, dst.base_code(), mem);
}
_ => {
return Err(invalid_operands(
"imul",
"unsupported operand combination",
instr.span,
))
}
}
Ok(())
}
3 => {
match (&ops[0], &ops[1], &ops[2]) {
(Operand::Register(dst), Operand::Register(src), Operand::Immediate(imm)) => {
let size = reg_size(*dst);
if size == 8 {
return Err(invalid_operands(
"imul",
"8-bit operands not supported for 2/3-operand IMUL",
instr.span,
));
}
let w = size == 64;
let r = dst.is_extended();
let b = src.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, false, b) {
buf.push(rex(w, r, false, b));
}
if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
buf.push(0x6B);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
buf.push(*imm as i8 as u8);
} else {
buf.push(0x69);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
let imm_size = if size > 32 { 32 } else { size };
emit_imm(buf, *imm, imm_size);
}
}
(Operand::Register(dst), Operand::Memory(mem), Operand::Immediate(imm)) => {
let size = reg_size(*dst);
if size == 8 {
return Err(invalid_operands(
"imul",
"8-bit operands not supported for 2/3-operand IMUL",
instr.span,
));
}
emit_rex_for_reg_mem(buf, *dst, mem)?;
if *imm >= i8::MIN as i128 && *imm <= i8::MAX as i128 {
buf.push(0x6B);
emit_mem_modrm(buf, dst.base_code(), mem);
buf.push(*imm as i8 as u8);
} else {
buf.push(0x69);
emit_mem_modrm(buf, dst.base_code(), mem);
let imm_size = if size > 32 { 32 } else { size };
emit_imm(buf, *imm, imm_size);
}
}
_ => {
return Err(invalid_operands(
"imul",
"expected reg, r/m, imm",
instr.span,
))
}
}
Ok(())
}
_ => Err(invalid_operands(
"imul",
"expected 1-3 operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_inc_dec(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
digit: u8,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 1 operand",
instr.span,
));
}
match &ops[0] {
Operand::Register(reg) => {
let size = reg_size(*reg);
let w = size == 64;
let b = reg.is_extended();
if size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, false, false, b) || reg.requires_rex_for_byte();
if need {
buf.push(rex(w, false, false, b));
}
buf.push(if size == 8 { 0xFE } else { 0xFF });
buf.push(modrm(0b11, digit, reg.base_code()));
}
Operand::Memory(mem) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
emit_rex_for_digit_mem(buf, size, mem);
buf.push(if size == 8 { 0xFE } else { 0xFF });
emit_mem_modrm(buf, digit, mem);
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected register or memory",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_shift(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
digit: u8,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 2 operands",
instr.span,
));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Immediate(1)) => {
let size = reg_size(*dst);
let w = size == 64;
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
if need {
buf.push(rex(w, false, false, b));
}
buf.push(if size == 8 { 0xD0 } else { 0xD1 });
buf.push(modrm(0b11, digit, dst.base_code()));
}
(Operand::Register(dst), Operand::Immediate(imm)) => {
let size = reg_size(*dst);
let w = size == 64;
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
if need {
buf.push(rex(w, false, false, b));
}
buf.push(if size == 8 { 0xC0 } else { 0xC1 });
buf.push(modrm(0b11, digit, dst.base_code()));
buf.push(*imm as u8);
}
(Operand::Register(dst), Operand::Register(Register::Cl)) => {
let size = reg_size(*dst);
let w = size == 64;
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, false, false, b) || dst.requires_rex_for_byte();
if need {
buf.push(rex(w, false, false, b));
}
buf.push(if size == 8 { 0xD2 } else { 0xD3 });
buf.push(modrm(0b11, digit, dst.base_code()));
}
(Operand::Memory(mem), Operand::Immediate(1)) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
let w = size == 64;
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, x, b) {
buf.push(rex(w, false, x, b));
}
buf.push(if size == 8 { 0xD0 } else { 0xD1 });
emit_mem_modrm(buf, digit, mem);
}
(Operand::Memory(mem), Operand::Immediate(imm)) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
let w = size == 64;
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, x, b) {
buf.push(rex(w, false, x, b));
}
buf.push(if size == 8 { 0xC0 } else { 0xC1 });
emit_mem_modrm(buf, digit, mem);
buf.push(*imm as u8);
}
(Operand::Memory(mem), Operand::Register(Register::Cl)) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
let w = size == 64;
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, x, b) {
buf.push(rex(w, false, x, b));
}
buf.push(if size == 8 { 0xD2 } else { 0xD3 });
emit_mem_modrm(buf, digit, mem);
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected r/m, imm or r/m, cl",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_jmp(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
relax: &mut Option<RelaxInfo>,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands("jmp", "expected 1 operand", instr.span));
}
match &ops[0] {
op @ (Operand::Label(_) | Operand::Expression(_)) => {
let Some((label, addend)) = extract_label(op) else {
return Err(invalid_operands("jmp", "expected label", instr.span));
};
buf.push(0xE9);
let reloc_off = buf.len();
buf.extend_from_slice(&0i32.to_le_bytes());
*reloc = Some(Relocation {
offset: reloc_off,
size: 4,
label: alloc::rc::Rc::from(label),
kind: RelocKind::X86Relative,
addend,
trailing_bytes: 0,
});
*relax = Some(RelaxInfo {
short_bytes: InstrBytes::from_slice(&[0xEB, 0x00]),
short_reloc_offset: 1,
short_relocation: None,
});
}
Operand::Immediate(target) => {
buf.push(0xE9);
buf.extend_from_slice(&(*target as i32).to_le_bytes());
}
Operand::Register(reg) => {
let b = reg.is_extended();
if b {
buf.push(rex(false, false, false, true));
}
buf.push(0xFF);
buf.push(modrm(0b11, 4, reg.base_code()));
}
Operand::Memory(mem) => {
emit_rex_for_digit_mem(buf, 0, mem);
buf.push(0xFF);
emit_mem_modrm(buf, 4, mem);
}
_ => return Err(invalid_operands("jmp", "unsupported operand", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_call(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands("call", "expected 1 operand", instr.span));
}
match &ops[0] {
op @ (Operand::Label(_) | Operand::Expression(_)) => {
let Some((label, addend)) = extract_label(op) else {
return Err(invalid_operands("call", "expected label", instr.span));
};
buf.push(0xE8);
let reloc_off = buf.len();
buf.extend_from_slice(&0i32.to_le_bytes());
*reloc = Some(Relocation {
offset: reloc_off,
size: 4,
label: alloc::rc::Rc::from(label),
kind: RelocKind::X86Relative,
addend,
trailing_bytes: 0,
});
}
Operand::Register(reg) => {
let b = reg.is_extended();
if b {
buf.push(rex(false, false, false, true));
}
buf.push(0xFF);
buf.push(modrm(0b11, 2, reg.base_code()));
}
Operand::Memory(mem) => {
emit_rex_for_digit_mem(buf, 0, mem);
buf.push(0xFF);
emit_mem_modrm(buf, 2, mem);
}
_ => return Err(invalid_operands("call", "unsupported operand", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_jcc(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
cc: u8,
reloc: &mut Option<Relocation>,
relax: &mut Option<RelaxInfo>,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 1 operand",
instr.span,
));
}
match &ops[0] {
op @ (Operand::Label(_) | Operand::Expression(_)) => {
let Some((label, addend)) = extract_label(op) else {
return Err(invalid_operands(
&instr.mnemonic,
"expected label or offset",
instr.span,
));
};
buf.push(0x0F);
buf.push(0x80 + cc);
let reloc_off = buf.len();
buf.extend_from_slice(&0i32.to_le_bytes());
*reloc = Some(Relocation {
offset: reloc_off,
size: 4,
label: alloc::rc::Rc::from(label),
kind: RelocKind::X86Relative,
addend,
trailing_bytes: 0,
});
*relax = Some(RelaxInfo {
short_bytes: InstrBytes::from_slice(&[0x70 + cc, 0x00]),
short_reloc_offset: 1,
short_relocation: None,
});
}
Operand::Immediate(off) => {
buf.push(0x0F);
buf.push(0x80 + cc);
buf.extend_from_slice(&(*off as i32).to_le_bytes());
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected label or offset",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_loop(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
opcode: u8,
reloc: &mut Option<Relocation>,
relax: &mut Option<RelaxInfo>,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 1 operand",
instr.span,
));
}
match &ops[0] {
op @ (Operand::Label(_) | Operand::Expression(_)) => {
let Some((label, addend)) = extract_label(op) else {
return Err(invalid_operands(
&instr.mnemonic,
"expected label",
instr.span,
));
};
buf.push(opcode);
buf.push(0x02);
buf.push(0xEB);
buf.push(0x05);
buf.push(0xE9);
let reloc_off = buf.len();
buf.extend_from_slice(&0i32.to_le_bytes());
*reloc = Some(Relocation {
offset: reloc_off,
size: 4,
label: alloc::rc::Rc::from(label),
kind: RelocKind::X86Relative,
addend,
trailing_bytes: 0,
});
*relax = Some(RelaxInfo {
short_bytes: InstrBytes::from_slice(&[opcode, 0x00]),
short_reloc_offset: 1,
short_relocation: None,
});
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected label",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_setcc(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
cc: u8,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 1 operand",
instr.span,
));
}
match &ops[0] {
Operand::Register(reg) => {
if reg.size_bits() != 8 {
return Err(invalid_operands(
&instr.mnemonic,
"SETcc requires an 8-bit register operand",
instr.span,
));
}
let b = reg.is_extended();
let need = b || reg.requires_rex_for_byte();
if need {
buf.push(rex(false, false, false, b));
}
buf.push(0x0F);
buf.push(0x90 + cc);
buf.push(modrm(0b11, 0, reg.base_code()));
}
Operand::Memory(mem) => {
emit_rex_for_digit_mem(buf, 8, mem);
buf.push(0x0F);
buf.push(0x90 + cc);
emit_mem_modrm(buf, 0, mem);
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected register or memory",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_cmovcc(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
cc: u8,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 2 operands",
instr.span,
));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
if size == 8 {
return Err(invalid_operands(
&instr.mnemonic,
"CMOVcc requires 16/32/64-bit operands",
instr.span,
));
}
let w = size == 64;
let r = dst.is_extended();
let b = src.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, false, b) {
buf.push(rex(w, r, false, b));
}
buf.push(0x0F);
buf.push(0x40 + cc);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
}
(Operand::Register(dst), Operand::Memory(mem)) => {
if reg_size(*dst) == 8 {
return Err(invalid_operands(
&instr.mnemonic,
"CMOVcc requires 16/32/64-bit operands",
instr.span,
));
}
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(0x0F);
buf.push(0x40 + cc);
emit_mem_modrm(buf, dst.base_code(), mem);
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected reg, r/m",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_movzx(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands("movzx", "expected 2 operands", instr.span));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let dst_size = reg_size(*dst);
let src_size = reg_size(*src);
let w = dst_size == 64;
let r = dst.is_extended();
let b = src.is_extended();
if dst_size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, r, false, b) || src.requires_rex_for_byte();
if need {
buf.push(rex(w, r, false, b));
}
buf.push(0x0F);
buf.push(if src_size == 8 { 0xB6 } else { 0xB7 }); buf.push(modrm(0b11, dst.base_code(), src.base_code()));
}
(Operand::Register(dst), Operand::Memory(mem)) => {
let src_size = instr
.size_hint
.map_or(mem.size.map_or(8u8, |s| s.bits() as u8), |s| s.bits() as u8);
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(0x0F);
buf.push(if src_size == 8 { 0xB6 } else { 0xB7 });
emit_mem_modrm(buf, dst.base_code(), mem);
}
_ => return Err(invalid_operands("movzx", "expected reg, r/m", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_movsx(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands("movsx", "expected 2 operands", instr.span));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let dst_size = reg_size(*dst);
let src_size = reg_size(*src);
let w = dst_size == 64;
let r = dst.is_extended();
let b = src.is_extended();
if dst_size == 16 {
buf.push(0x66);
}
let need = needs_rex(w, r, false, b) || src.requires_rex_for_byte();
if need {
buf.push(rex(w, r, false, b));
}
if src_size == 32 {
buf.push(0x63);
} else {
buf.push(0x0F);
buf.push(if src_size == 8 { 0xBE } else { 0xBF });
}
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
}
(Operand::Register(dst), Operand::Memory(mem)) => {
let src_size = instr
.size_hint
.map_or(mem.size.map_or(8u8, |s| s.bits() as u8), |s| s.bits() as u8);
let dst_size = reg_size(*dst);
if src_size == 32 {
let w = dst_size == 64;
let r = dst.is_extended();
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if dst_size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, x, b) {
buf.push(rex(w, r, x, b));
}
buf.push(0x63);
} else {
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(0x0F);
buf.push(if src_size == 8 { 0xBE } else { 0xBF });
}
emit_mem_modrm(buf, dst.base_code(), mem);
}
_ => return Err(invalid_operands("movsx", "expected reg, r/m", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_xchg(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands("xchg", "expected 2 operands", instr.span));
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
if size == 16
&& ((dst.base_code() == 0 && !dst.is_extended())
|| (src.base_code() == 0 && !src.is_extended()))
{
let other = if dst.base_code() == 0 && !dst.is_extended() {
*src
} else {
*dst
};
let b = other.is_extended();
buf.push(0x66);
if b {
buf.push(rex(false, false, false, b));
}
buf.push(0x90 + other.base_code());
return Ok(());
}
if size >= 32
&& ((dst.base_code() == 0 && !dst.is_extended())
|| (src.base_code() == 0 && !src.is_extended()))
{
let other = if dst.base_code() == 0 && !dst.is_extended() {
*src
} else {
*dst
};
let w = size == 64;
let b = other.is_extended();
if needs_rex(w, false, false, b) {
buf.push(rex(w, false, false, b));
}
buf.push(0x90 + other.base_code());
return Ok(());
}
let opcode = if size == 8 { 0x86u8 } else { 0x87u8 };
emit_rr(buf, &[opcode], *dst, *src, instr.span)?;
}
(Operand::Register(reg), Operand::Memory(mem)) => {
let size = reg_size(*reg);
let opcode = if size == 8 { 0x86u8 } else { 0x87u8 };
emit_rex_for_reg_mem(buf, *reg, mem)?;
buf.push(opcode);
emit_mem_modrm(buf, reg.base_code(), mem);
}
(Operand::Memory(mem), Operand::Register(reg)) => {
let size = reg_size(*reg);
let opcode = if size == 8 { 0x86u8 } else { 0x87u8 };
emit_rex_for_reg_mem(buf, *reg, mem)?;
buf.push(opcode);
emit_mem_modrm(buf, reg.base_code(), mem);
}
_ => {
return Err(invalid_operands(
"xchg",
"unsupported operand combination",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_bt(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
digit: u8,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 2 operands",
instr.span,
));
}
if let Operand::Register(r) = &ops[0] {
if reg_size(*r) == 8 {
return Err(invalid_operands(
&instr.mnemonic,
"8-bit operands not supported",
instr.span,
));
}
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Immediate(imm)) => {
let size = reg_size(*dst);
let w = size == 64;
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, false, b) {
buf.push(rex(w, false, false, b));
}
buf.push(0x0F);
buf.push(0xBA);
buf.push(modrm(0b11, digit, dst.base_code()));
buf.push(*imm as u8);
}
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
let w = size == 64;
let r = src.is_extended();
let b = dst.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, false, b) {
buf.push(rex(w, r, false, b));
}
buf.push(0x0F);
let base = match digit {
4 => 0xA3, 5 => 0xAB, 6 => 0xB3, 7 => 0xBB, _ => 0xA3,
};
buf.push(base);
buf.push(modrm(0b11, src.base_code(), dst.base_code()));
}
(Operand::Memory(mem), Operand::Register(src)) => {
let size = mem.size.map_or(reg_size(*src), |s| s.bits() as u8);
let w = size == 64;
let r = src.is_extended();
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, x, b) {
buf.push(rex(w, r, x, b));
}
buf.push(0x0F);
let base = match digit {
4 => 0xA3,
5 => 0xAB,
6 => 0xB3,
7 => 0xBB,
_ => 0xA3,
};
buf.push(base);
emit_mem_modrm(buf, src.base_code(), mem);
}
(Operand::Memory(mem), Operand::Immediate(imm)) => {
let size = instr
.size_hint
.map_or(mem.size.map_or(32u8, |s| s.bits() as u8), |s| {
s.bits() as u8
});
let w = size == 64;
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, false, x, b) {
buf.push(rex(w, false, x, b));
}
buf.push(0x0F);
buf.push(0xBA);
emit_mem_modrm(buf, digit, mem);
buf.push(*imm as u8);
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"unsupported operand combination",
instr.span,
))
}
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_bsf_bsr(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
opcode2: u8,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands(
&instr.mnemonic,
"expected 2 operands",
instr.span,
));
}
if let Operand::Register(r) = &ops[0] {
if reg_size(*r) == 8 {
return Err(invalid_operands(
&instr.mnemonic,
"8-bit operands not supported",
instr.span,
));
}
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
let w = size == 64;
let r = dst.is_extended();
let b = src.is_extended();
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, false, b) {
buf.push(rex(w, r, false, b));
}
buf.push(0x0F);
buf.push(opcode2);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
}
(Operand::Register(dst), Operand::Memory(mem)) => {
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(0x0F);
buf.push(opcode2);
emit_mem_modrm(buf, dst.base_code(), mem);
}
_ => {
return Err(invalid_operands(
&instr.mnemonic,
"expected reg, r/m",
instr.span,
))
}
}
Ok(())
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn encode_f3_0f_rm(
buf: &mut InstrBytes,
ops: &OperandList,
mnemonic: &str,
opcode: u8,
span: Span,
) -> Result<(), AsmError> {
if ops.len() != 2 {
return Err(invalid_operands(mnemonic, "expected 2 operands", span));
}
if let Operand::Register(r) = &ops[0] {
if reg_size(*r) == 8 {
return Err(invalid_operands(
mnemonic,
"8-bit operands not supported",
span,
));
}
}
match (&ops[0], &ops[1]) {
(Operand::Register(dst), Operand::Register(src)) => {
let size = reg_size(*dst);
let w = size == 64;
let r = dst.is_extended();
let b = src.is_extended();
buf.push(0xF3);
if size == 16 {
buf.push(0x66);
}
if needs_rex(w, r, false, b) {
buf.push(rex(w, r, false, b));
}
buf.push(0x0F);
buf.push(opcode);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
}
(Operand::Register(dst), Operand::Memory(mem)) => {
buf.push(0xF3);
emit_rex_for_reg_mem(buf, *dst, mem)?;
buf.push(0x0F);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
}
_ => return Err(invalid_operands(mnemonic, "expected reg, r/m", span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_popcnt(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
encode_f3_0f_rm(buf, ops, "popcnt", 0xB8, instr.span)
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_lzcnt(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
encode_f3_0f_rm(buf, ops, "lzcnt", 0xBD, instr.span)
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_tzcnt(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
encode_f3_0f_rm(buf, ops, "tzcnt", 0xBC, instr.span)
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_bswap(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
if ops.len() != 1 {
return Err(invalid_operands("bswap", "expected 1 operand", instr.span));
}
match &ops[0] {
Operand::Register(reg) => {
let size = reg_size(*reg);
if size == 8 {
return Err(invalid_operands(
"bswap",
"8-bit operands not supported",
instr.span,
));
}
if size == 16 {
return Err(invalid_operands(
"bswap",
"16-bit bswap has undefined behavior; use xchg or rol instead",
instr.span,
));
}
let w = size == 64;
let b = reg.is_extended();
if needs_rex(w, false, false, b) {
buf.push(rex(w, false, false, b));
}
buf.push(0x0F);
buf.push(0xC8 + reg.base_code());
}
_ => return Err(invalid_operands("bswap", "expected register", instr.span)),
}
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn emit_imm(buf: &mut InstrBytes, imm: i128, size: u8) {
debug_assert!(
matches!(size, 8 | 16 | 32 | 64),
"emit_imm: unsupported immediate size {size} (expected 8, 16, 32, or 64)"
);
match size {
0..=8 => buf.push(imm as u8),
9..=16 => buf.extend_from_slice(&(imm as u16).to_le_bytes()),
17..=32 => buf.extend_from_slice(&(imm as u32).to_le_bytes()),
_ => buf.extend_from_slice(&(imm as u64).to_le_bytes()),
}
}
#[inline]
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn invalid_operands(_mnemonic: &str, detail: &str, span: Span) -> AsmError {
AsmError::InvalidOperands {
detail: String::from(detail),
span,
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_rex_sse_rr(buf: &mut InstrBytes, w: bool, reg: Register, rm: Register) {
let r = reg.is_extended();
let b = rm.is_extended();
if needs_rex(w, r, false, b) {
buf.push(rex(w, r, false, b));
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_rex_sse_rm(buf: &mut InstrBytes, w: bool, reg: Register, mem: &MemoryOperand) {
let r = reg.is_extended();
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if needs_rex(w, r, x, b) {
buf.push(rex(w, r, x, b));
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_sse_rr(
buf: &mut InstrBytes,
mandatory_prefix: u8,
opcode: &[u8],
dst: Register,
src: Register,
rex_w: bool,
) {
if mandatory_prefix != 0 {
buf.push(mandatory_prefix);
}
emit_rex_sse_rr(buf, rex_w, dst, src);
buf.extend_from_slice(opcode);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_sse_rm(
buf: &mut InstrBytes,
mandatory_prefix: u8,
opcode: &[u8],
reg: Register,
mem: &MemoryOperand,
reloc: &mut Option<Relocation>,
rex_w: bool,
) {
if mandatory_prefix != 0 {
buf.push(mandatory_prefix);
}
emit_rex_sse_rm(buf, rex_w, reg, mem);
buf.extend_from_slice(opcode);
let disp_off = emit_mem_modrm(buf, reg.base_code(), mem);
set_mem_reloc(reloc, mem, disp_off, buf.len());
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_sse_mr(
buf: &mut InstrBytes,
mandatory_prefix: u8,
opcode: &[u8],
mem: &MemoryOperand,
reg: Register,
reloc: &mut Option<Relocation>,
rex_w: bool,
) {
encode_sse_rm(buf, mandatory_prefix, opcode, reg, mem, reloc, rex_w);
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_sse_op(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
mandatory_prefix: u8,
load_opcode: &[u8],
store_opcode: Option<&[u8]>,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(dst)), Some(Operand::Register(src)))
if dst.is_xmm() && src.is_xmm() =>
{
encode_sse_rr(buf, mandatory_prefix, load_opcode, *dst, *src, false);
Ok(())
}
(Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
encode_sse_rm(buf, mandatory_prefix, load_opcode, *dst, mem, reloc, false);
Ok(())
}
(Some(Operand::Memory(mem)), Some(Operand::Register(src))) if src.is_xmm() => {
let opcode = store_opcode.unwrap_or(load_opcode);
encode_sse_mr(buf, mandatory_prefix, opcode, mem, *src, reloc, false);
Ok(())
}
_ => Err(invalid_operands(
instr.mnemonic.as_str(),
"expected xmm,xmm/m or m,xmm operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_sse_imm(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
mandatory_prefix: u8,
opcode: &[u8],
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
let imm = match ops.get(2) {
Some(Operand::Immediate(v)) => *v,
_ => {
return Err(invalid_operands(
instr.mnemonic.as_str(),
"expected xmm, xmm/m, imm8",
instr.span,
));
}
};
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(dst)), Some(Operand::Register(src)))
if dst.is_xmm() && src.is_xmm() =>
{
encode_sse_rr(buf, mandatory_prefix, opcode, *dst, *src, false);
}
(Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
encode_sse_rm(buf, mandatory_prefix, opcode, *dst, mem, reloc, false);
}
_ => {
return Err(invalid_operands(
instr.mnemonic.as_str(),
"expected xmm, xmm/m, imm8",
instr.span,
));
}
}
buf.push(imm as u8);
Ok(())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_movd_movq(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
reloc: &mut Option<Relocation>,
is_movq: bool,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(dst)), Some(Operand::Register(src)))
if dst.is_xmm() && !src.is_xmm() =>
{
let w = is_movq || src.size_bits() == 64;
encode_sse_rr(buf, 0x66, &[0x0F, 0x6E], *dst, *src, w);
Ok(())
}
(Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
let w = is_movq;
encode_sse_rm(buf, 0x66, &[0x0F, 0x6E], *dst, mem, reloc, w);
Ok(())
}
(Some(Operand::Register(dst)), Some(Operand::Register(src)))
if !dst.is_xmm() && src.is_xmm() =>
{
let w = is_movq || dst.size_bits() == 64;
encode_sse_rr(buf, 0x66, &[0x0F, 0x7E], *src, *dst, w);
Ok(())
}
(Some(Operand::Memory(mem)), Some(Operand::Register(src))) if src.is_xmm() => {
let w = is_movq;
encode_sse_mr(buf, 0x66, &[0x0F, 0x7E], mem, *src, reloc, w);
Ok(())
}
(Some(Operand::Register(dst)), Some(Operand::Register(src)))
if dst.is_xmm() && src.is_xmm() =>
{
encode_sse_rr(buf, 0xF3, &[0x0F, 0x7E], *dst, *src, false);
Ok(())
}
_ => Err(invalid_operands(
instr.mnemonic.as_str(),
"expected xmm,r/m or r/m,xmm operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_cvtsi2(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
mandatory_prefix: u8,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(dst)), Some(Operand::Register(src)))
if dst.is_xmm() && !src.is_xmm() =>
{
let w = src.size_bits() == 64;
encode_sse_rr(buf, mandatory_prefix, &[0x0F, 0x2A], *dst, *src, w);
Ok(())
}
(Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if dst.is_xmm() => {
let w = mem.size == Some(OperandSize::Qword);
encode_sse_rm(buf, mandatory_prefix, &[0x0F, 0x2A], *dst, mem, reloc, w);
Ok(())
}
_ => Err(invalid_operands(
instr.mnemonic.as_str(),
"expected xmm, r/m32 or xmm, r/m64",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_cvt2si(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
mandatory_prefix: u8,
opcode2: u8,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(dst)), Some(Operand::Register(src)))
if !dst.is_xmm() && src.is_xmm() =>
{
let w = dst.size_bits() == 64;
encode_sse_rr(buf, mandatory_prefix, &[0x0F, opcode2], *dst, *src, w);
Ok(())
}
(Some(Operand::Register(dst)), Some(Operand::Memory(mem))) if !dst.is_xmm() => {
let w = dst.size_bits() == 64;
encode_sse_rm(buf, mandatory_prefix, &[0x0F, opcode2], *dst, mem, reloc, w);
Ok(())
}
_ => Err(invalid_operands(
instr.mnemonic.as_str(),
"expected r32/r64, xmm/m operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_prefetch(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
digit: u8,
) -> Result<(), AsmError> {
match ops.first() {
Some(Operand::Memory(mem)) => {
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if needs_rex(false, false, x, b) {
buf.push(rex(false, false, x, b));
}
buf.extend_from_slice(&[0x0F, 0x18]);
emit_mem_modrm(buf, digit, mem);
Ok(())
}
_ => Err(invalid_operands(
instr.mnemonic.as_str(),
"expected memory operand",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_clflush(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match ops.first() {
Some(Operand::Memory(mem)) => {
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if needs_rex(false, false, x, b) {
buf.push(rex(false, false, x, b));
}
buf.extend_from_slice(&[0x0F, 0xAE]);
emit_mem_modrm(buf, 7, mem);
Ok(())
}
_ => Err(invalid_operands(
"clflush",
"expected memory operand",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_clflushopt(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match ops.first() {
Some(Operand::Memory(mem)) => {
buf.push(0x66);
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if needs_rex(false, false, x, b) {
buf.push(rex(false, false, x, b));
}
buf.extend_from_slice(&[0x0F, 0xAE]);
emit_mem_modrm(buf, 7, mem);
Ok(())
}
_ => Err(invalid_operands(
"clflushopt",
"expected memory operand",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_clwb(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match ops.first() {
Some(Operand::Memory(mem)) => {
buf.push(0x66);
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if needs_rex(false, false, x, b) {
buf.push(rex(false, false, x, b));
}
buf.extend_from_slice(&[0x0F, 0xAE]);
emit_mem_modrm(buf, 6, mem);
Ok(())
}
_ => Err(invalid_operands(
"clwb",
"expected memory operand",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_prefetchw(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match ops.first() {
Some(Operand::Memory(mem)) => {
let x = mem.index.is_some_and(|r| r.is_extended());
let b = mem.base.is_some_and(|r| r.is_extended());
if needs_rex(false, false, x, b) {
buf.push(rex(false, false, x, b));
}
buf.extend_from_slice(&[0x0F, 0x0D]);
emit_mem_modrm(buf, 1, mem);
Ok(())
}
_ => Err(invalid_operands(
"prefetchw",
"expected memory operand",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_crc32(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
) -> Result<(), AsmError> {
match (ops.first(), ops.get(1)) {
(Some(Operand::Register(dst)), Some(Operand::Register(src))) => {
let dst_s = dst.size_bits();
let src_s = src.size_bits();
if dst_s != 32 && dst_s != 64 {
return Err(invalid_operands(
"crc32",
"destination must be r32 or r64",
instr.span,
));
}
buf.push(0xF2);
let w = dst_s == 64;
let opcode2 = if src_s == 8 { 0xF0u8 } else { 0xF1 };
if src_s == 16 {
buf.push(0x66);
}
emit_rex_sse_rr(buf, w, *dst, *src);
buf.extend_from_slice(&[0x0F, 0x38, opcode2]);
buf.push(modrm(0b11, dst.base_code(), src.base_code()));
Ok(())
}
(Some(Operand::Register(dst)), Some(Operand::Memory(mem))) => {
let dst_s = dst.size_bits();
if dst_s != 32 && dst_s != 64 {
return Err(invalid_operands(
"crc32",
"destination must be r32 or r64",
instr.span,
));
}
let src_s = instr.size_hint.map_or_else(
|| mem.size.map_or(32u8, |s| s.bits() as u8),
|s| s.bits() as u8,
);
buf.push(0xF2);
let w = dst_s == 64;
let opcode2 = if src_s == 8 { 0xF0u8 } else { 0xF1 };
if src_s == 16 {
buf.push(0x66);
}
emit_rex_sse_rm(buf, w, *dst, mem);
buf.extend_from_slice(&[0x0F, 0x38, opcode2]);
emit_mem_modrm(buf, dst.base_code(), mem);
Ok(())
}
_ => Err(invalid_operands(
"crc32",
"expected r32/r64, r/m operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn vex_pp(mandatory_prefix: u8) -> u8 {
match mandatory_prefix {
0x00 => 0b00,
0x66 => 0b01,
0xF3 => 0b10,
0xF2 => 0b11,
_ => 0b00,
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn vex_mmmmm(escape: &[u8]) -> u8 {
match escape {
[0x0F] => 0b00001,
[0x0F, 0x38] => 0b00010,
[0x0F, 0x3A] => 0b00011,
_ => 0b00001,
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_vex2(buf: &mut InstrBytes, r: bool, vvvv: u8, l: bool, pp: u8) {
let byte1 = (if r { 0 } else { 0x80 })
| (((!vvvv) & 0x0F) << 3)
| (if l { 0x04 } else { 0 })
| (pp & 0x03);
buf.push(0xC5);
buf.push(byte1);
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_vex3(
buf: &mut InstrBytes,
r: bool,
x: bool,
b: bool,
mmmmm: u8,
w: bool,
vvvv: u8,
l: bool,
pp: u8,
) {
let byte1 = (if r { 0 } else { 0x80 })
| (if x { 0 } else { 0x40 })
| (if b { 0 } else { 0x20 })
| (mmmmm & 0x1F);
let byte2 = (if w { 0x80 } else { 0 })
| (((!vvvv) & 0x0F) << 3)
| (if l { 0x04 } else { 0 })
| (pp & 0x03);
buf.push(0xC4);
buf.push(byte1);
buf.push(byte2);
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_vex_prefix(
buf: &mut InstrBytes,
reg_extended: bool,
x_extended: bool,
rm_extended: bool,
w: bool,
vvvv: u8,
l: bool,
pp: u8,
escape: &[u8],
) {
let mmmmm = vex_mmmmm(escape);
if mmmmm == 0b00001 && !w && !x_extended && !rm_extended {
emit_vex2(buf, reg_extended, vvvv, l, pp);
} else {
emit_vex3(
buf,
reg_extended,
x_extended,
rm_extended,
mmmmm,
w,
vvvv,
l,
pp,
);
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_rrr(
buf: &mut InstrBytes,
pp: u8,
escape: &[u8],
opcode: u8,
dst: Register,
src1: Register,
src2: Register,
w: bool,
l: bool,
) {
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src2.is_extended(),
w,
src1.base_code() | if src1.is_extended() { 8 } else { 0 },
l,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_rrm(
buf: &mut InstrBytes,
pp: u8,
escape: &[u8],
opcode: u8,
dst: Register,
src1: Register,
mem: &MemoryOperand,
reloc: &mut Option<Relocation>,
w: bool,
l: bool,
) {
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
src1.base_code() | if src1.is_extended() { 8 } else { 0 },
l,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
if let Some(ref mut rel) = reloc {
rel.offset = buf.len() - 4;
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_op(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
load_opcode: u8,
store_opcode: Option<u8>,
w: bool,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
use Operand::*;
let o = (ops.first(), ops.get(1), ops.get(2));
match o {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
{
let l = dst.is_ymm() || src1.is_ymm();
encode_vex_rrr(buf, pp, escape, load_opcode, *dst, *src1, *src2, w, l);
Ok(())
}
(Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
if dst.is_vector() && src1.is_vector() =>
{
let l = dst.is_ymm() || src1.is_ymm();
encode_vex_rrm(buf, pp, escape, load_opcode, *dst, *src1, mem, reloc, w, l);
Ok(())
}
(Some(Register(dst)), Some(Register(src)), None) if dst.is_vector() && src.is_vector() => {
let l = dst.is_ymm() || src.is_ymm();
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src.is_extended(),
w,
0,
l,
mandatory_pp,
escape,
);
buf.push(load_opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
Ok(())
}
(Some(Register(dst)), Some(Memory(mem)), None) if dst.is_vector() => {
let l = dst.is_ymm();
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
0,
l,
mandatory_pp,
escape,
);
buf.push(load_opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
Ok(())
}
(Some(Memory(mem)), Some(Register(src)), None)
if src.is_vector() && store_opcode.is_some() =>
{
let l = src.is_ymm();
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
src.is_extended(),
x_ext,
b_ext,
w,
0,
l,
mandatory_pp,
escape,
);
buf.push(store_opcode.unwrap_or(0));
emit_mem_modrm(buf, src.base_code(), mem);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected VEX xmm/ymm operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_imm(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
opcode: u8,
w: bool,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
use Operand::*;
let o = (ops.first(), ops.get(1), ops.get(2), ops.get(3));
match o {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2)), Some(Immediate(imm)))
if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
{
let l = dst.is_ymm() || src1.is_ymm();
encode_vex_rrr(buf, pp, escape, opcode, *dst, *src1, *src2, w, l);
buf.push(*imm as u8);
Ok(())
}
(Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)), Some(Immediate(imm)))
if dst.is_vector() && src1.is_vector() =>
{
let l = dst.is_ymm() || src1.is_ymm();
encode_vex_rrm(buf, pp, escape, opcode, *dst, *src1, mem, reloc, w, l);
buf.push(*imm as u8);
Ok(())
}
(Some(Register(dst)), Some(Register(src)), Some(Immediate(imm)), None)
if dst.is_vector() && src.is_vector() =>
{
let l = dst.is_ymm() || src.is_ymm();
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src.is_extended(),
w,
0,
l,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
buf.push(*imm as u8);
Ok(())
}
(Some(Register(dst)), Some(Memory(mem)), Some(Immediate(imm)), None) if dst.is_vector() => {
let l = dst.is_ymm();
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
0,
l,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
buf.push(*imm as u8);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected VEX xmm/ymm operands with imm8",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_bmi_vex_ndd(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
opcode: u8,
w_from_size: bool,
) -> Result<(), AsmError> {
use Operand::*;
match (ops.first(), ops.get(1), ops.get(2)) {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src2.is_extended(),
w,
src1.base_code() | if src1.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
Ok(())
}
(Some(Register(dst)), Some(Register(src1)), Some(Memory(mem))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
src1.base_code() | if src1.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
Ok(())
}
(Some(Register(dst)), Some(Register(src)), None) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
false, false,
src.is_extended(),
w,
dst.base_code() | if dst.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | src.base_code());
Ok(())
}
(Some(Register(dst)), Some(Memory(mem)), None) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
false,
x_ext,
b_ext,
w,
dst.base_code() | if dst.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, 0, mem);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected GP register operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_bmi_digit(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
opcode: u8,
digit: u8,
w_from_size: bool,
) -> Result<(), AsmError> {
use Operand::*;
match (ops.first(), ops.get(1)) {
(Some(Register(dst)), Some(Register(src))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
false, false,
src.is_extended(),
w,
dst.base_code() | if dst.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (digit << 3) | src.base_code());
Ok(())
}
(Some(Register(dst)), Some(Memory(mem))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
false,
x_ext,
b_ext,
w,
dst.base_code() | if dst.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, digit, mem);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected r32/r64, r/m32/r/m64",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_bmi_imm(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
opcode: u8,
w_from_size: bool,
) -> Result<(), AsmError> {
use Operand::*;
match (ops.first(), ops.get(1), ops.get(2)) {
(Some(Register(dst)), Some(Register(src)), Some(Immediate(imm))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src.is_extended(),
w,
0,
false,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
buf.push(*imm as u8);
Ok(())
}
(Some(Register(dst)), Some(Memory(mem)), Some(Immediate(imm))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
0,
false,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
buf.push(*imm as u8);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected r, r/m, imm8",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_bmi_rmv(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
opcode: u8,
w_from_size: bool,
) -> Result<(), AsmError> {
use Operand::*;
match (ops.first(), ops.get(1), ops.get(2)) {
(Some(Register(dst)), Some(Register(src)), Some(Register(ctrl))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src.is_extended(),
w,
ctrl.base_code() | if ctrl.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
Ok(())
}
(Some(Register(dst)), Some(Memory(mem)), Some(Register(ctrl))) => {
let w = if w_from_size {
dst.size_bits() == 64
} else {
false
};
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
ctrl.base_code() | if ctrl.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected r, r/m, r",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_shift(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
reg_opcode: u8,
imm_opcode: u8,
digit: u8,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
use Operand::*;
match (ops.first(), ops.get(1), ops.get(2)) {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
{
let l = dst.is_ymm() || src1.is_ymm();
encode_vex_rrr(buf, pp, escape, reg_opcode, *dst, *src1, *src2, false, l);
Ok(())
}
(Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
if dst.is_vector() && src1.is_vector() =>
{
let l = dst.is_ymm() || src1.is_ymm();
encode_vex_rrm(
buf, pp, escape, reg_opcode, *dst, *src1, mem, reloc, false, l,
);
Ok(())
}
(Some(Register(dst)), Some(Register(src)), Some(Immediate(imm)))
if dst.is_vector() && src.is_vector() =>
{
let l = dst.is_ymm() || src.is_ymm();
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
false, false,
src.is_extended(),
false, dst.base_code() | if dst.is_extended() { 8 } else { 0 },
l,
mandatory_pp,
escape,
);
buf.push(imm_opcode);
buf.push(0xC0 | (digit << 3) | src.base_code());
buf.push(*imm as u8);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected VEX xmm/ymm shift operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_vex_cvt(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
escape: &[u8],
opcode: u8,
_reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
use Operand::*;
match (ops.first(), ops.get(1), ops.get(2)) {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
if dst.is_xmm() && src1.is_xmm() =>
{
let w = src2.size_bits() == 64;
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src2.is_extended(),
w,
src1.base_code() | if src1.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
Ok(())
}
(Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
if dst.is_xmm() && src1.is_xmm() =>
{
let w = mem.size.is_some_and(|s| s.bits() == 64);
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
src1.base_code() | if src1.is_extended() { 8 } else { 0 },
false,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
Ok(())
}
(Some(Register(dst)), Some(Register(src)), None) if !dst.is_vector() && src.is_xmm() => {
let w = dst.size_bits() == 64;
let mandatory_pp = vex_pp(pp);
emit_vex_prefix(
buf,
dst.is_extended(),
false,
src.is_extended(),
w,
0,
false,
mandatory_pp,
escape,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
Ok(())
}
(Some(Register(dst)), Some(Memory(mem)), None) if !dst.is_vector() => {
let w = dst.size_bits() == 64;
let mandatory_pp = vex_pp(pp);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_vex_prefix(
buf,
dst.is_extended(),
x_ext,
b_ext,
w,
0,
false,
mandatory_pp,
escape,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected conversion operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_evex(
buf: &mut InstrBytes,
r_ext: bool,
x_ext: bool,
b_ext: bool,
r_prime: bool,
mm: u8,
w: bool,
vvvv: u8,
v_prime: bool,
pp: u8,
z: bool,
ll: u8,
b_bit: bool,
aaa: u8,
) {
let p0 = (if r_ext { 0 } else { 0x80 })
| (if x_ext { 0 } else { 0x40 })
| (if b_ext { 0 } else { 0x20 })
| (if r_prime { 0 } else { 0x10 })
| (mm & 0x03);
let p1 = (if w { 0x80 } else { 0 })
| (((!vvvv) & 0x0F) << 3)
| 0x04 | (pp & 0x03);
let p2 = (if z { 0x80 } else { 0 })
| ((ll & 0x03) << 5)
| (if b_bit { 0x10 } else { 0 })
| (if v_prime { 0 } else { 0x08 })
| (aaa & 0x07);
buf.push(0x62);
buf.push(p0);
buf.push(p1);
buf.push(p2);
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn evex_reg_bits(reg: Register) -> (u8, bool, bool) {
(reg.base_code(), reg.is_extended(), reg.is_evex_extended())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn evex_ll(reg: Register) -> u8 {
if reg.is_zmm() {
2
} else if reg.is_ymm() {
1
} else {
0
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_evex_prefix_rrr(
buf: &mut InstrBytes,
dst: Register,
src1: Register,
src2: Register,
mm: u8,
w: bool,
pp: u8,
ll: u8,
aaa: u8,
z: bool,
) {
let (_, dst_ext, dst_evex) = evex_reg_bits(dst);
let (src1_code, src1_ext, src1_evex) = evex_reg_bits(src1);
let (_, src2_ext, src2_evex) = evex_reg_bits(src2);
let vvvv = src1_code | if src1_ext { 8 } else { 0 };
emit_evex(
buf, dst_ext, src2_evex, src2_ext, dst_evex, mm, w, vvvv, src1_evex, pp, z, ll, false, aaa,
);
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn emit_evex_prefix_rrm(
buf: &mut InstrBytes,
dst: Register,
src1: Register,
mem: &MemoryOperand,
mm: u8,
w: bool,
pp: u8,
ll: u8,
b_bit: bool,
aaa: u8,
z: bool,
) {
let (_, dst_ext, dst_evex) = evex_reg_bits(dst);
let (src1_code, src1_ext, src1_evex) = evex_reg_bits(src1);
let vvvv = src1_code | if src1_ext { 8 } else { 0 };
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_evex(
buf, dst_ext, x_ext, b_ext, dst_evex, mm, w, vvvv, src1_evex, pp, z, ll, b_bit, aaa,
);
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn evex_aaa(instr: &Instruction) -> u8 {
instr.opmask.map_or(0, |reg| reg.base_code())
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
fn evex_broadcast_bit(instr: &Instruction) -> bool {
instr.broadcast.is_some()
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_evex_op(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
mm: u8,
load_opcode: u8,
store_opcode: Option<u8>,
w: bool,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
use Operand::*;
let mandatory_pp = vex_pp(pp);
let o = (ops.first(), ops.get(1), ops.get(2));
match o {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
{
let ll = evex_ll(*dst);
let aaa = evex_aaa(instr);
emit_evex_prefix_rrr(
buf,
*dst,
*src1,
*src2,
mm,
w,
mandatory_pp,
ll,
aaa,
instr.zeroing,
);
buf.push(load_opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
Ok(())
}
(Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)))
if dst.is_vector() && src1.is_vector() =>
{
let ll = evex_ll(*dst);
let aaa = evex_aaa(instr);
let b_bit = evex_broadcast_bit(instr);
emit_evex_prefix_rrm(
buf,
*dst,
*src1,
mem,
mm,
w,
mandatory_pp,
ll,
b_bit,
aaa,
instr.zeroing,
);
buf.push(load_opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
if let Some(ref mut rel) = reloc {
rel.offset = buf.len() - 4;
}
Ok(())
}
(Some(Register(dst)), Some(Register(src)), None) if dst.is_vector() && src.is_vector() => {
let ll = evex_ll(*dst);
let aaa = evex_aaa(instr);
let (_, src_ext, src_evex) = evex_reg_bits(*src);
let (_, dst_ext, dst_evex) = evex_reg_bits(*dst);
emit_evex(
buf,
dst_ext,
src_evex,
src_ext,
dst_evex,
mm,
w,
0,
false,
mandatory_pp,
instr.zeroing,
ll,
false,
aaa,
);
buf.push(load_opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
Ok(())
}
(Some(Register(dst)), Some(Memory(mem)), None) if dst.is_vector() => {
let ll = evex_ll(*dst);
let aaa = evex_aaa(instr);
let b_bit = evex_broadcast_bit(instr);
let (_, dst_ext, dst_evex) = evex_reg_bits(*dst);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_evex(
buf,
dst_ext,
x_ext,
b_ext,
dst_evex,
mm,
w,
0,
false,
mandatory_pp,
instr.zeroing,
ll,
b_bit,
aaa,
);
buf.push(load_opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
if let Some(ref mut rel) = reloc {
rel.offset = buf.len() - 4;
}
Ok(())
}
(Some(Memory(mem)), Some(Register(src)), None)
if src.is_vector() && store_opcode.is_some() =>
{
let ll = evex_ll(*src);
let aaa = evex_aaa(instr);
let (_, src_ext, src_evex) = evex_reg_bits(*src);
let x_ext = mem.index.is_some_and(|r| r.is_extended());
let b_ext = mem.base.is_some_and(|r| r.is_extended());
emit_evex(
buf,
src_ext,
x_ext,
b_ext,
src_evex,
mm,
w,
0,
false,
mandatory_pp,
instr.zeroing,
ll,
false,
aaa,
);
buf.push(store_opcode.unwrap_or(0));
emit_mem_modrm(buf, src.base_code(), mem);
if let Some(ref mut rel) = reloc {
rel.offset = buf.len() - 4;
}
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected EVEX zmm/ymm/xmm operands",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
pub(crate) fn encode_evex_imm(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
mm: u8,
opcode: u8,
w: bool,
reloc: &mut Option<Relocation>,
) -> Result<(), AsmError> {
use Operand::*;
let mandatory_pp = vex_pp(pp);
match (ops.first(), ops.get(1), ops.get(2), ops.get(3)) {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2)), Some(Immediate(imm)))
if dst.is_vector() && src1.is_vector() && src2.is_vector() =>
{
let ll = evex_ll(*dst);
let aaa = evex_aaa(instr);
emit_evex_prefix_rrr(
buf,
*dst,
*src1,
*src2,
mm,
w,
mandatory_pp,
ll,
aaa,
instr.zeroing,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
buf.push(*imm as u8);
Ok(())
}
(Some(Register(dst)), Some(Register(src1)), Some(Memory(mem)), Some(Immediate(imm)))
if dst.is_vector() && src1.is_vector() =>
{
let ll = evex_ll(*dst);
let aaa = evex_aaa(instr);
let b_bit = evex_broadcast_bit(instr);
emit_evex_prefix_rrm(
buf,
*dst,
*src1,
mem,
mm,
w,
mandatory_pp,
ll,
b_bit,
aaa,
instr.zeroing,
);
buf.push(opcode);
emit_mem_modrm(buf, dst.base_code(), mem);
if let Some(ref mut rel) = reloc {
rel.offset = buf.len() - 4;
}
buf.push(*imm as u8);
Ok(())
}
(Some(Register(dst)), Some(Register(src)), Some(Immediate(imm)), None)
if dst.is_vector() && src.is_vector() =>
{
let ll = evex_ll(*dst);
let aaa = evex_aaa(instr);
let (_, src_ext, src_evex) = evex_reg_bits(*src);
let (_, dst_ext, dst_evex) = evex_reg_bits(*dst);
emit_evex(
buf,
dst_ext,
src_evex,
src_ext,
dst_evex,
mm,
w,
0,
false,
mandatory_pp,
instr.zeroing,
ll,
false,
aaa,
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
buf.push(*imm as u8);
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected EVEX operands with immediate",
instr.span,
)),
}
}
#[cfg(any(feature = "x86", feature = "x86_64"))]
#[allow(dead_code)]
pub(crate) fn encode_evex_opmask(
buf: &mut InstrBytes,
ops: &OperandList,
instr: &Instruction,
pp: u8,
_mm: u8,
opcode: u8,
w: bool,
) -> Result<(), AsmError> {
use Operand::*;
let mandatory_pp = vex_pp(pp);
match (ops.first(), ops.get(1), ops.get(2)) {
(Some(Register(dst)), Some(Register(src1)), Some(Register(src2)))
if dst.is_opmask() && src1.is_opmask() && src2.is_opmask() =>
{
let src1_vvvv = src1.base_code();
emit_vex_prefix(
buf,
false,
false,
false,
w,
src1_vvvv,
true,
mandatory_pp,
&[0x0F],
);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src2.base_code());
Ok(())
}
(Some(Register(dst)), Some(Register(src)), None) if dst.is_opmask() && src.is_opmask() => {
emit_vex_prefix(buf, false, false, false, w, 0, true, mandatory_pp, &[0x0F]);
buf.push(opcode);
buf.push(0xC0 | (dst.base_code() << 3) | src.base_code());
Ok(())
}
_ => Err(invalid_operands(
&instr.mnemonic,
"expected opmask register operands (k0-k7)",
instr.span,
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Span;
fn span() -> Span {
Span::new(1, 1, 0, 0)
}
fn make_instr(mnemonic: &str, operands: Vec<Operand>) -> Instruction {
Instruction {
mnemonic: Mnemonic::from(mnemonic),
operands: OperandList::from(operands),
size_hint: None,
prefixes: PrefixList::new(),
opmask: None,
zeroing: false,
broadcast: None,
span: span(),
}
}
fn make_instr_with_hint(
mnemonic: &str,
operands: Vec<Operand>,
hint: Option<OperandSize>,
) -> Instruction {
Instruction {
mnemonic: Mnemonic::from(mnemonic),
operands: OperandList::from(operands),
size_hint: hint,
prefixes: PrefixList::new(),
opmask: None,
zeroing: false,
broadcast: None,
span: span(),
}
}
fn encode(mnemonic: &str, operands: Vec<Operand>) -> Vec<u8> {
let instr = make_instr(mnemonic, operands);
encode_instruction(&instr, Arch::X86_64)
.unwrap()
.bytes
.to_vec()
}
fn encode_with_hint(mnemonic: &str, operands: Vec<Operand>, hint: OperandSize) -> Vec<u8> {
let mut instr = make_instr(mnemonic, operands);
instr.size_hint = Some(hint);
encode_instruction(&instr, Arch::X86_64)
.unwrap()
.bytes
.to_vec()
}
fn encode_with_prefix(mnemonic: &str, operands: Vec<Operand>, prefix: Prefix) -> Vec<u8> {
let mut instr = make_instr(mnemonic, operands);
instr.prefixes = PrefixList::from(alloc::vec![prefix]);
encode_instruction(&instr, Arch::X86_64)
.unwrap()
.bytes
.to_vec()
}
use crate::ir::Register::*;
use Operand::*;
#[test]
fn test_nop() {
assert_eq!(encode("nop", vec![]), vec![0x90]);
}
#[test]
fn test_ret() {
assert_eq!(encode("ret", vec![]), vec![0xC3]);
}
#[test]
fn test_syscall() {
assert_eq!(encode("syscall", vec![]), vec![0x0F, 0x05]);
}
#[test]
fn test_int3() {
assert_eq!(encode("int3", vec![]), vec![0xCC]);
}
#[test]
fn test_int_0x80() {
assert_eq!(encode("int", vec![Immediate(0x80)]), vec![0xCD, 0x80]);
}
#[test]
fn test_hlt() {
assert_eq!(encode("hlt", vec![]), vec![0xF4]);
}
#[test]
fn test_mov_rax_rbx() {
let bytes = encode("mov", vec![Register(Rax), Register(Rbx)]);
assert_eq!(bytes, vec![0x48, 0x89, 0xD8]);
}
#[test]
fn test_mov_eax_ecx() {
let bytes = encode("mov", vec![Register(Eax), Register(Ecx)]);
assert_eq!(bytes, vec![0x89, 0xC8]);
}
#[test]
fn test_mov_r8_r9() {
let bytes = encode("mov", vec![Register(R8), Register(R9)]);
assert_eq!(bytes, vec![0x4D, 0x89, 0xC8]);
}
#[test]
fn test_mov_al_bl() {
let bytes = encode("mov", vec![Register(Al), Register(Bl)]);
assert_eq!(bytes, vec![0x88, 0xD8]);
}
#[test]
fn test_mov_eax_1() {
let bytes = encode("mov", vec![Register(Eax), Immediate(1)]);
assert_eq!(bytes, vec![0xB8, 0x01, 0x00, 0x00, 0x00]);
}
#[test]
fn test_mov_al_0xff() {
let bytes = encode("mov", vec![Register(Al), Immediate(0xFF)]);
assert_eq!(bytes, vec![0xB0, 0xFF]);
}
#[test]
fn test_mov_rax_small_imm() {
let bytes = encode("mov", vec![Register(Rax), Immediate(1)]);
assert_eq!(bytes, vec![0xB8, 0x01, 0x00, 0x00, 0x00]);
}
#[test]
fn test_mov_rax_neg1() {
let bytes = encode("mov", vec![Register(Rax), Immediate(-1)]);
assert_eq!(bytes, vec![0x48, 0xC7, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn test_mov_rax_large_imm() {
let bytes = encode("mov", vec![Register(Rax), Immediate(0x0102030405060708)]);
assert_eq!(
bytes,
vec![0x48, 0xB8, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
);
}
#[test]
fn test_mov_r8d_imm() {
let bytes = encode("mov", vec![Register(R8d), Immediate(42)]);
assert_eq!(bytes, vec![0x41, 0xB8, 0x2A, 0x00, 0x00, 0x00]);
}
#[test]
fn test_mov_rax_mem_rbx() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0x48, 0x8B, 0x03]);
}
#[test]
fn test_mov_mem_rbx_rax() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("mov", vec![Memory(Box::new(mem)), Register(Rax)]);
assert_eq!(bytes, vec![0x48, 0x89, 0x03]);
}
#[test]
fn test_mov_rax_mem_rbp_disp8() {
let mem = MemoryOperand {
base: Some(Rbp),
disp: 8,
..Default::default()
};
let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0x48, 0x8B, 0x45, 0x08]);
}
#[test]
fn test_mov_rax_mem_rbp_disp32() {
let mem = MemoryOperand {
base: Some(Rbp),
disp: 0x200,
..Default::default()
};
let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0x48, 0x8B, 0x85, 0x00, 0x02, 0x00, 0x00]);
}
#[test]
fn test_push_rbp() {
assert_eq!(encode("push", vec![Register(Rbp)]), vec![0x55]);
}
#[test]
fn test_push_r12() {
assert_eq!(encode("push", vec![Register(R12)]), vec![0x41, 0x54]);
}
#[test]
fn test_pop_rbp() {
assert_eq!(encode("pop", vec![Register(Rbp)]), vec![0x5D]);
}
#[test]
fn test_push_imm8() {
assert_eq!(encode("push", vec![Immediate(1)]), vec![0x6A, 0x01]);
}
#[test]
fn test_push_imm32() {
assert_eq!(
encode("push", vec![Immediate(0x1000)]),
vec![0x68, 0x00, 0x10, 0x00, 0x00]
);
}
#[test]
fn test_add_rax_rbx() {
let bytes = encode("add", vec![Register(Rax), Register(Rbx)]);
assert_eq!(bytes, vec![0x48, 0x01, 0xD8]); }
#[test]
fn test_add_eax_1() {
let bytes = encode("add", vec![Register(Eax), Immediate(1)]);
assert_eq!(bytes, vec![0x83, 0xC0, 0x01]);
}
#[test]
fn test_sub_rsp_8() {
let bytes = encode("sub", vec![Register(Rsp), Immediate(8)]);
assert_eq!(bytes, vec![0x48, 0x83, 0xEC, 0x08]);
}
#[test]
fn test_xor_eax_eax() {
let bytes = encode("xor", vec![Register(Eax), Register(Eax)]);
assert_eq!(bytes, vec![0x31, 0xC0]);
}
#[test]
fn test_cmp_rax_0() {
let bytes = encode("cmp", vec![Register(Rax), Immediate(0)]);
assert_eq!(bytes, vec![0x48, 0x83, 0xF8, 0x00]);
}
#[test]
fn test_and_al_imm() {
let bytes = encode("and", vec![Register(Al), Immediate(0x0F)]);
assert_eq!(bytes, vec![0x24, 0x0F]);
}
#[test]
fn test_or_eax_large_imm() {
let bytes = encode("or", vec![Register(Eax), Immediate(0x1000)]);
assert_eq!(bytes, vec![0x0D, 0x00, 0x10, 0x00, 0x00]);
}
#[test]
fn test_test_al_imm() {
let bytes = encode("test", vec![Register(Al), Immediate(1)]);
assert_eq!(bytes, vec![0xA8, 0x01]);
}
#[test]
fn test_test_eax_eax() {
let bytes = encode("test", vec![Register(Eax), Register(Eax)]);
assert_eq!(bytes, vec![0x85, 0xC0]);
}
#[test]
fn test_shl_eax_1() {
let bytes = encode("shl", vec![Register(Eax), Immediate(1)]);
assert_eq!(bytes, vec![0xD1, 0xE0]);
}
#[test]
fn test_shr_rcx_4() {
let bytes = encode("shr", vec![Register(Rcx), Immediate(4)]);
assert_eq!(bytes, vec![0x48, 0xC1, 0xE9, 0x04]);
}
#[test]
fn test_sar_rax_cl() {
let bytes = encode("sar", vec![Register(Rax), Register(Cl)]);
assert_eq!(bytes, vec![0x48, 0xD3, 0xF8]);
}
#[test]
fn test_inc_rax() {
let bytes = encode("inc", vec![Register(Rax)]);
assert_eq!(bytes, vec![0x48, 0xFF, 0xC0]);
}
#[test]
fn test_dec_ecx() {
let bytes = encode("dec", vec![Register(Ecx)]);
assert_eq!(bytes, vec![0xFF, 0xC9]);
}
#[test]
fn test_neg_rax() {
let bytes = encode("neg", vec![Register(Rax)]);
assert_eq!(bytes, vec![0x48, 0xF7, 0xD8]);
}
#[test]
fn test_not_eax() {
let bytes = encode("not", vec![Register(Eax)]);
assert_eq!(bytes, vec![0xF7, 0xD0]);
}
#[test]
fn test_jmp_label_relocation() {
let instr = make_instr("jmp", vec![Label(String::from("target"))]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes, vec![0xE9, 0x00, 0x00, 0x00, 0x00]);
assert!(result.relocation.is_some());
let r = result.relocation.unwrap();
assert_eq!(&*r.label, "target");
assert_eq!(r.kind, RelocKind::X86Relative);
assert_eq!(r.size, 4);
}
#[test]
fn test_jmp_reg() {
let bytes = encode("jmp", vec![Register(Rax)]);
assert_eq!(bytes, vec![0xFF, 0xE0]);
}
#[test]
fn test_call_reg() {
let bytes = encode("call", vec![Register(Rax)]);
assert_eq!(bytes, vec![0xFF, 0xD0]);
}
#[test]
fn test_call_r12() {
let bytes = encode("call", vec![Register(R12)]);
assert_eq!(bytes, vec![0x41, 0xFF, 0xD4]);
}
#[test]
fn test_je_label() {
let instr = make_instr("je", vec![Label(String::from("target"))]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0..2], [0x0F, 0x84]);
assert!(result.relocation.is_some());
}
#[test]
fn test_jne_label() {
let instr = make_instr("jne", vec![Label(String::from("target"))]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0..2], [0x0F, 0x85]);
}
#[test]
fn test_sete_al() {
let bytes = encode("sete", vec![Register(Al)]);
assert_eq!(bytes, vec![0x0F, 0x94, 0xC0]);
}
#[test]
fn test_cmove_rax_rbx() {
let bytes = encode("cmove", vec![Register(Rax), Register(Rbx)]);
assert_eq!(bytes, vec![0x48, 0x0F, 0x44, 0xC3]);
}
#[test]
fn test_movzx_eax_al() {
let bytes = encode("movzx", vec![Register(Eax), Register(Al)]);
assert_eq!(bytes, vec![0x0F, 0xB6, 0xC0]);
}
#[test]
fn test_movsx_rax_eax() {
let bytes = encode("movsx", vec![Register(Rax), Register(Eax)]);
assert_eq!(bytes, vec![0x48, 0x63, 0xC0]);
}
#[test]
fn test_lea_rax_rbx_rcx_8() {
let mem = MemoryOperand {
base: Some(Rbx),
index: Some(Rcx),
scale: 8,
disp: 0,
..Default::default()
};
let bytes = encode("lea", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0x48, 0x8D, 0x04, 0xCB]);
}
#[test]
fn test_lock_prefix() {
let mem = MemoryOperand {
base: Some(Rax),
..Default::default()
};
let bytes = encode_with_prefix(
"add",
vec![Memory(Box::new(mem)), Immediate(1)],
Prefix::Lock,
);
assert_eq!(bytes[0], 0xF0); }
#[test]
fn test_imul_r_r() {
let bytes = encode("imul", vec![Register(Rax), Register(Rbx)]);
assert_eq!(bytes, vec![0x48, 0x0F, 0xAF, 0xC3]);
}
#[test]
fn test_imul_r_r_imm8() {
let bytes = encode("imul", vec![Register(Rax), Register(Rbx), Immediate(10)]);
assert_eq!(bytes, vec![0x48, 0x6B, 0xC3, 0x0A]);
}
#[test]
fn test_bswap_eax() {
let bytes = encode("bswap", vec![Register(Eax)]);
assert_eq!(bytes, vec![0x0F, 0xC8]);
}
#[test]
fn test_bswap_rax() {
let bytes = encode("bswap", vec![Register(Rax)]);
assert_eq!(bytes, vec![0x48, 0x0F, 0xC8]);
}
#[test]
fn test_rep_movsb() {
let mut instr = make_instr("movsb", vec![]);
instr.prefixes = PrefixList::from(alloc::vec![Prefix::Rep]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes, vec![0xF3, 0xA4]);
}
#[test]
fn test_nop3_encoding() {
let instr = make_instr("nop3", vec![]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes, vec![0x0F, 0x1F, 0x00]);
}
#[test]
fn test_mov_rax_mem_rsp() {
let mem = MemoryOperand {
base: Some(Rsp),
..Default::default()
};
let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0x48, 0x8B, 0x04, 0x24]);
}
#[test]
fn test_cdq() {
assert_eq!(encode("cdq", vec![]), vec![0x99]);
}
#[test]
fn test_cqo() {
assert_eq!(encode("cqo", vec![]), vec![0x48, 0x99]);
}
#[test]
fn test_mov_ax_bx() {
let bytes = encode("mov", vec![Register(Ax), Register(Bx)]);
assert_eq!(bytes, vec![0x66, 0x89, 0xD8]);
}
#[test]
fn test_add_ax_imm() {
let bytes = encode("add", vec![Register(Ax), Immediate(1)]);
assert_eq!(bytes, vec![0x66, 0x83, 0xC0, 0x01]);
}
#[test]
fn test_arch_x86_nop() {
let instr = make_instr("nop", vec![]);
let result = encode_instruction(&instr, Arch::X86);
assert!(result.is_ok());
assert_eq!(result.unwrap().bytes, vec![0x90]);
}
#[test]
fn test_x86_32_inc_eax() {
let instr = make_instr("inc", vec![Register(Eax)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0x40]);
}
#[test]
fn test_x86_32_inc_ebx() {
let instr = make_instr("inc", vec![Register(Ebx)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0x43]);
}
#[test]
fn test_x86_32_inc_edi() {
let instr = make_instr("inc", vec![Register(Edi)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0x47]);
}
#[test]
fn test_x86_32_dec_eax() {
let instr = make_instr("dec", vec![Register(Eax)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0x48]);
}
#[test]
fn test_x86_32_dec_esp() {
let instr = make_instr("dec", vec![Register(Esp)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0x4C]);
}
#[test]
fn test_x86_32_inc_ax() {
let instr = make_instr("inc", vec![Register(Ax)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0x66, 0x40]);
}
#[test]
fn test_x86_32_dec_cx() {
let instr = make_instr("dec", vec![Register(Cx)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0x66, 0x49]);
}
#[test]
fn test_x86_32_inc_al_uses_modrm() {
let instr = make_instr("inc", vec![Register(Al)]);
let result = encode_instruction(&instr, Arch::X86).unwrap();
assert_eq!(result.bytes, vec![0xFE, 0xC0]);
}
#[test]
fn test_push_ax() {
let bytes = encode("push", vec![Register(Ax)]);
assert_eq!(bytes, vec![0x66, 0x50]);
}
#[test]
fn test_pop_ax() {
let bytes = encode("pop", vec![Register(Ax)]);
assert_eq!(bytes, vec![0x66, 0x58]);
}
#[test]
fn test_push_bx() {
let bytes = encode("push", vec![Register(Bx)]);
assert_eq!(bytes, vec![0x66, 0x53]);
}
#[test]
fn test_pop_bx() {
let bytes = encode("pop", vec![Register(Bx)]);
assert_eq!(bytes, vec![0x66, 0x5B]);
}
#[test]
fn test_xchg_ax_bx_shortcut() {
let bytes = encode("xchg", vec![Register(Ax), Register(Bx)]);
assert_eq!(bytes, vec![0x66, 0x93]);
}
#[test]
fn test_movsx_eax_byte_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode_with_hint(
"movsx",
vec![Register(Eax), Memory(Box::new(mem))],
OperandSize::Byte,
);
assert_eq!(bytes, vec![0x0F, 0xBE, 0x03]);
}
#[test]
fn test_movsx_rax_word_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode_with_hint(
"movsx",
vec![Register(Rax), Memory(Box::new(mem))],
OperandSize::Word,
);
assert_eq!(bytes, vec![0x48, 0x0F, 0xBF, 0x03]);
}
#[test]
fn test_movsxd_rax_dword_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode_with_hint(
"movsx",
vec![Register(Rax), Memory(Box::new(mem))],
OperandSize::Dword,
);
assert_eq!(bytes, vec![0x48, 0x63, 0x03]);
}
#[test]
fn test_bsf_eax_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("bsf", vec![Register(Eax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0x0F, 0xBC, 0x03]);
}
#[test]
fn test_bsr_rax_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("bsr", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0x48, 0x0F, 0xBD, 0x03]);
}
#[test]
fn test_popcnt_eax_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("popcnt", vec![Register(Eax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0xF3, 0x0F, 0xB8, 0x03]);
}
#[test]
fn test_lzcnt_rax_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("lzcnt", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0xF3, 0x48, 0x0F, 0xBD, 0x03]);
}
#[test]
fn test_tzcnt_eax_mem() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("tzcnt", vec![Register(Eax), Memory(Box::new(mem))]);
assert_eq!(bytes, vec![0xF3, 0x0F, 0xBC, 0x03]);
}
#[test]
fn test_bt_mem_reg() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode("bt", vec![Memory(Box::new(mem)), Register(Eax)]);
assert_eq!(bytes, vec![0x0F, 0xA3, 0x03]);
}
#[test]
fn test_bts_mem_imm() {
let mem = MemoryOperand {
base: Some(Rbx),
size: Some(OperandSize::Dword),
..Default::default()
};
let bytes = encode("bts", vec![Memory(Box::new(mem)), Immediate(5)]);
assert_eq!(bytes, vec![0x0F, 0xBA, 0x2B, 0x05]);
}
#[test]
fn test_shl_mem_1() {
let mem = MemoryOperand {
base: Some(Rbx),
size: Some(OperandSize::Dword),
..Default::default()
};
let bytes = encode("shl", vec![Memory(Box::new(mem)), Immediate(1)]);
assert_eq!(bytes, vec![0xD1, 0x23]);
}
#[test]
fn test_shr_mem_imm() {
let mem = MemoryOperand {
base: Some(Rbx),
size: Some(OperandSize::Dword),
..Default::default()
};
let bytes = encode("shr", vec![Memory(Box::new(mem)), Immediate(4)]);
assert_eq!(bytes, vec![0xC1, 0x2B, 0x04]);
}
#[test]
fn test_sar_mem_cl() {
let mem = MemoryOperand {
base: Some(Rbx),
size: Some(OperandSize::Dword),
..Default::default()
};
let bytes = encode("sar", vec![Memory(Box::new(mem)), Register(Cl)]);
assert_eq!(bytes, vec![0xD3, 0x3B]);
}
#[test]
fn test_mov_xmm_imm_error() {
let instr = make_instr("mov", vec![Register(Xmm0), Immediate(1)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_mov_rax_label_expression() {
let expr = Expr::Add(
Box::new(Expr::Label(String::from("data"))),
Box::new(Expr::Num(8)),
);
let instr = make_instr("mov", vec![Register(Rax), Expression(expr)]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0], 0x48); assert_eq!(result.bytes[1], 0xB8); let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "data");
assert_eq!(reloc.addend, 8);
assert_eq!(reloc.size, 8);
assert_eq!(reloc.kind, RelocKind::Absolute);
}
#[test]
fn test_jmp_label_expression() {
let expr = Expr::Sub(
Box::new(Expr::Label(String::from("target"))),
Box::new(Expr::Num(2)),
);
let instr = make_instr("jmp", vec![Expression(expr)]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0], 0xE9); let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "target");
assert_eq!(reloc.addend, -2);
assert_eq!(reloc.kind, RelocKind::X86Relative);
assert!(result.relax.is_some()); }
#[test]
fn test_call_label_expression() {
let expr = Expr::Add(
Box::new(Expr::Label(String::from("func"))),
Box::new(Expr::Num(4)),
);
let instr = make_instr("call", vec![Expression(expr)]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0], 0xE8); let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "func");
assert_eq!(reloc.addend, 4);
}
#[test]
fn test_jcc_label_expression() {
let expr = Expr::Add(
Box::new(Expr::Label(String::from("dest"))),
Box::new(Expr::Num(0)),
);
let instr = make_instr("je", vec![Expression(expr)]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0], 0x0F); let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "dest");
assert_eq!(reloc.addend, 0);
}
#[test]
fn test_push_label_expression() {
let expr = Expr::Add(
Box::new(Expr::Label(String::from("data"))),
Box::new(Expr::Num(16)),
);
let instr = make_instr("push", vec![Expression(expr)]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0], 0x68); let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "data");
assert_eq!(reloc.addend, 16);
}
#[test]
fn test_loop_label_expression() {
let expr = Expr::Sub(
Box::new(Expr::Label(String::from("top"))),
Box::new(Expr::Num(1)),
);
let instr = make_instr("loop", vec![Expression(expr)]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0], 0xE2); let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "top");
assert_eq!(reloc.addend, -1);
assert_eq!(reloc.size, 4); assert!(result.relax.is_some());
let ri = result.relax.unwrap();
assert_eq!(ri.short_bytes[0], 0xE2);
assert_eq!(ri.short_bytes.len(), 2);
}
#[test]
fn test_extract_label_plain() {
let op = Label(String::from("foo"));
assert_eq!(extract_label(&op), Some(("foo", 0)));
}
#[test]
fn test_extract_label_expression() {
let expr = Expr::Add(
Box::new(Expr::Label(String::from("bar"))),
Box::new(Expr::Num(10)),
);
assert_eq!(extract_label(&Expression(expr)), Some(("bar", 10)));
}
#[test]
fn test_extract_label_non_label() {
assert_eq!(extract_label(&Immediate(42)), None);
assert_eq!(extract_label(&Register(Rax)), None);
}
#[test]
fn test_imul_reg_mem_imm8() {
let mem = MemoryOperand {
base: Some(Rcx),
..Default::default()
};
let bytes = encode(
"imul",
vec![Register(Eax), Memory(Box::new(mem)), Immediate(5)],
);
assert_eq!(bytes, vec![0x6B, 0x01, 0x05]);
}
#[test]
fn test_imul_reg_mem_imm32() {
let mem = MemoryOperand {
base: Some(Rdx),
..Default::default()
};
let bytes = encode(
"imul",
vec![Register(Rax), Memory(Box::new(mem)), Immediate(1000)],
);
assert_eq!(bytes, vec![0x48, 0x69, 0x02, 0xE8, 0x03, 0x00, 0x00]);
}
#[test]
fn test_ret_imm16() {
let bytes = encode("ret", vec![Immediate(8)]);
assert_eq!(bytes, vec![0xC2, 0x08, 0x00]);
}
#[test]
fn test_ret_imm16_large() {
let bytes = encode("ret", vec![Immediate(0x1234)]);
assert_eq!(bytes, vec![0xC2, 0x34, 0x12]);
}
#[test]
fn test_retn_alias() {
assert_eq!(encode("retn", vec![]), vec![0xC3]);
assert_eq!(encode("retn", vec![Immediate(4)]), vec![0xC2, 0x04, 0x00]);
}
#[test]
fn test_retf() {
assert_eq!(encode("retf", vec![]), vec![0xCB]);
}
#[test]
fn test_retf_imm16() {
let bytes = encode("retf", vec![Immediate(4)]);
assert_eq!(bytes, vec![0xCA, 0x04, 0x00]);
}
#[test]
fn test_lret_alias() {
assert_eq!(encode("lret", vec![]), vec![0xCB]);
assert_eq!(encode("lret", vec![Immediate(8)]), vec![0xCA, 0x08, 0x00]);
}
#[test]
fn test_movabs_alias() {
let bytes_mov = encode("mov", vec![Register(Rax), Immediate(0x12345678)]);
let bytes_movabs = encode("movabs", vec![Register(Rax), Immediate(0x12345678)]);
assert_eq!(bytes_mov, bytes_movabs);
}
#[test]
fn test_movabs_imm64() {
let bytes = encode("movabs", vec![Register(Rax), Immediate(0x0102030405060708)]);
assert_eq!(
bytes,
vec![0x48, 0xB8, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01]
);
}
#[test]
fn test_high_byte_rex_conflict_rejected() {
let instr = make_instr("mov", vec![Register(Ah), Register(Sil)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
let err = result.unwrap_err();
match err {
AsmError::InvalidOperands { detail, .. } => {
assert!(detail.contains("high-byte"));
}
other => panic!("expected InvalidOperands, got {:?}", other),
}
}
#[test]
fn test_high_byte_extended_reg_conflict_rejected() {
let instr = make_instr("add", vec![Register(Ah), Register(R8b)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_high_byte_without_rex_ok() {
let bytes = encode("mov", vec![Register(Ah), Register(Al)]);
assert_eq!(bytes, vec![0x88, 0xC4]);
}
#[test]
fn test_high_byte_pair_ok() {
let bytes = encode("xor", vec![Register(Ah), Register(Ch)]);
assert_eq!(bytes, vec![0x30, 0xEC]);
}
#[test]
fn test_lock_valid_memory_dest() {
let mem = MemoryOperand {
base: Some(Rax),
..Default::default()
};
let bytes = encode_with_prefix(
"add",
vec![Memory(Box::new(mem)), Immediate(1)],
Prefix::Lock,
);
assert_eq!(bytes[0], 0xF0);
}
#[test]
fn test_lock_invalid_reg_dest() {
let mut instr = make_instr("add", vec![Register(Eax), Register(Ebx)]);
instr.prefixes = PrefixList::from(alloc::vec![Prefix::Lock]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
match result.unwrap_err() {
AsmError::InvalidOperands { detail, .. } => {
assert!(detail.contains("LOCK"));
}
other => panic!("expected InvalidOperands for LOCK, got {:?}", other),
}
}
#[test]
fn test_lock_invalid_imm_dest() {
let mut instr = make_instr("xchg", vec![Register(Eax), Register(Ecx)]);
instr.prefixes = PrefixList::from(alloc::vec![Prefix::Lock]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_push_fs() {
assert_eq!(encode("push", vec![Register(Fs)]), vec![0x0F, 0xA0]);
}
#[test]
fn test_push_gs() {
assert_eq!(encode("push", vec![Register(Gs)]), vec![0x0F, 0xA8]);
}
#[test]
fn test_pop_fs() {
assert_eq!(encode("pop", vec![Register(Fs)]), vec![0x0F, 0xA1]);
}
#[test]
fn test_pop_gs() {
assert_eq!(encode("pop", vec![Register(Gs)]), vec![0x0F, 0xA9]);
}
#[test]
fn test_xchg_eax_eax_is_nop() {
let bytes = encode("xchg", vec![Register(Eax), Register(Eax)]);
assert_eq!(bytes, vec![0x90]);
}
#[test]
fn test_xchg_rax_rax() {
let bytes = encode("xchg", vec![Register(Rax), Register(Rax)]);
assert_eq!(bytes, vec![0x48, 0x90]);
}
#[test]
fn test_xchg_ax_ax() {
let bytes = encode("xchg", vec![Register(Ax), Register(Ax)]);
assert_eq!(bytes, vec![0x66, 0x90]);
}
#[test]
fn test_mov_ah_imm8() {
let bytes = encode("mov", vec![Register(Ah), Immediate(0x42)]);
assert_eq!(bytes, vec![0xB4, 0x42]);
}
#[test]
fn test_mov_ch_imm8() {
let bytes = encode("mov", vec![Register(Ch), Immediate(0x11)]);
assert_eq!(bytes, vec![0xB5, 0x11]);
}
#[test]
fn test_mov_dh_imm8() {
let bytes = encode("mov", vec![Register(Dh), Immediate(0x22)]);
assert_eq!(bytes, vec![0xB6, 0x22]);
}
#[test]
fn test_mov_bh_imm8() {
let bytes = encode("mov", vec![Register(Bh), Immediate(0x33)]);
assert_eq!(bytes, vec![0xB7, 0x33]);
}
#[test]
fn test_shl_byte_ptr_mem_1() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let bytes = encode_with_hint(
"shl",
vec![Memory(Box::new(mem)), Immediate(1)],
OperandSize::Byte,
);
assert_eq!(bytes[0], 0xD0); }
#[test]
fn test_shr_qword_ptr_mem_cl() {
let mem = MemoryOperand {
base: Some(Rax),
..Default::default()
};
let bytes = encode_with_hint(
"shr",
vec![Memory(Box::new(mem)), Register(Cl)],
OperandSize::Qword,
);
assert_eq!(bytes[0], 0x48); assert_eq!(bytes[1], 0xD3); }
#[test]
fn test_shl_word_ptr_mem_imm() {
let mem = MemoryOperand {
base: Some(Rcx),
..Default::default()
};
let bytes = encode_with_hint(
"shl",
vec![Memory(Box::new(mem)), Immediate(4)],
OperandSize::Word,
);
assert_eq!(bytes[0], 0x66); assert_eq!(bytes[1], 0xC1); }
#[test]
fn test_push_eax_rejected() {
let instr = make_instr("push", vec![Register(Eax)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_pop_eax_rejected() {
let instr = make_instr("pop", vec![Register(Eax)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_push_al_rejected() {
let instr = make_instr("push", vec![Register(Al)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_push_cs_rejected() {
let instr = make_instr("push", vec![Register(Cs)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_pop_ds_rejected() {
let instr = make_instr("pop", vec![Register(Ds)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_cmove_8bit_rejected() {
let instr = make_instr("cmove", vec![Register(Al), Register(Bl)]);
let result = encode_instruction(&instr, Arch::X86_64);
assert!(result.is_err());
}
#[test]
fn test_add_rax_mem_label_reloc() {
let mem = MemoryOperand {
base: Some(Rip),
disp_label: Some(alloc::string::String::from("my_data")),
addr_mode: AddrMode::Offset,
..Default::default()
};
let instr = make_instr("add", vec![Register(Rax), Memory(Box::new(mem))]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert!(
result.relocation.is_some(),
"expected relocation for add rax, [my_data]"
);
let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "my_data");
assert_eq!(reloc.kind, RelocKind::X86Relative);
}
#[test]
fn test_cmp_mem_label_imm_reloc() {
let mem = MemoryOperand {
base: Some(Rip),
disp_label: Some(alloc::string::String::from("counter")),
addr_mode: AddrMode::Offset,
..Default::default()
};
let mut instr = make_instr("cmp", vec![Memory(Box::new(mem)), Immediate(0)]);
instr.size_hint = Some(OperandSize::Dword);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert!(
result.relocation.is_some(),
"expected relocation for cmp [counter], 0"
);
let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "counter");
}
#[test]
fn test_mov_mem_label_reg_reloc() {
let mem = MemoryOperand {
base: Some(Rip),
disp_label: Some(alloc::string::String::from("my_var")),
addr_mode: AddrMode::Offset,
..Default::default()
};
let instr = make_instr("mov", vec![Memory(Box::new(mem)), Register(Rax)]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert!(
result.relocation.is_some(),
"expected relocation for mov [my_var], rax"
);
let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "my_var");
assert_eq!(reloc.kind, RelocKind::X86Relative);
}
#[test]
fn test_mov_mem_label_imm_reloc() {
let mem = MemoryOperand {
base: Some(Rip),
disp_label: Some(alloc::string::String::from("flag")),
addr_mode: AddrMode::Offset,
..Default::default()
};
let mut instr = make_instr("mov", vec![Memory(Box::new(mem)), Immediate(1)]);
instr.size_hint = Some(OperandSize::Dword);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert!(
result.relocation.is_some(),
"expected relocation for mov [flag], 1"
);
let reloc = result.relocation.unwrap();
assert_eq!(&*reloc.label, "flag");
assert_eq!(reloc.kind, RelocKind::X86Relative);
}
#[test]
fn test_test_mem_label_reloc() {
let mem = MemoryOperand {
base: Some(Rip),
disp_label: Some(alloc::string::String::from("status")),
addr_mode: AddrMode::Offset,
..Default::default()
};
let mut instr = make_instr("test", vec![Memory(Box::new(mem)), Immediate(1)]);
instr.size_hint = Some(OperandSize::Dword);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert!(
result.relocation.is_some(),
"expected relocation for test [status], 1"
);
}
#[test]
fn test_segment_override_fs_prefix_position() {
let mem = MemoryOperand {
base: Some(Rbx),
segment: Some(Fs),
..Default::default()
};
let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes[0], 0x64, "FS segment override must be first byte");
assert_eq!(bytes[1], 0x48, "REX.W must follow segment override");
assert_eq!(bytes[2], 0x8B, "opcode must follow REX");
}
#[test]
fn test_segment_override_gs_prefix_position() {
let mem = MemoryOperand {
base: Some(Rdx),
segment: Some(Gs),
..Default::default()
};
let bytes = encode("mov", vec![Register(Eax), Memory(Box::new(mem))]);
assert_eq!(bytes[0], 0x65, "GS segment override must be first byte");
assert_eq!(bytes[1], 0x8B, "opcode must follow segment override");
}
#[test]
fn test_segment_override_with_extended_reg() {
let mem = MemoryOperand {
base: Some(R12),
segment: Some(Fs),
..Default::default()
};
let bytes = encode("add", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes[0], 0x64, "FS must precede REX");
assert_eq!(bytes[1] & 0xF0, 0x40, "REX must follow segment override");
}
#[test]
fn test_sib_index_only_disp0() {
let mem = MemoryOperand {
index: Some(Rsi),
scale: 4,
..Default::default()
};
let bytes = encode("mov", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes.len(), 8, "SIB index-only must include 4-byte disp32");
assert_eq!(bytes[0], 0x48); assert_eq!(bytes[1], 0x8B); assert_eq!(bytes[2] & 0xC7, 0x04); assert_eq!(bytes[3], 0xB5); assert_eq!(&bytes[4..8], &[0, 0, 0, 0]); }
#[test]
fn test_sib_index_only_with_disp() {
let mem = MemoryOperand {
index: Some(Rdi),
scale: 8,
disp: 16,
..Default::default()
};
let bytes = encode("lea", vec![Register(Rax), Memory(Box::new(mem))]);
assert_eq!(bytes.len(), 8, "SIB index-only with disp must use disp32");
assert_eq!(bytes[2] & 0xC0, 0x00, "mod must be 00 (disp32, no base)");
assert_eq!(&bytes[4..8], &(16i32).to_le_bytes());
}
#[test]
fn test_sib_index_only_extended_index() {
let mem = MemoryOperand {
index: Some(R9),
scale: 2,
..Default::default()
};
let bytes = encode("mov", vec![Register(Eax), Memory(Box::new(mem))]);
assert_eq!(bytes.len(), 8, "SIB index-only with extended index");
assert_eq!(bytes[0] & 0x42, 0x42, "REX.X must be set for R9 index");
}
#[test]
fn test_bt_rejects_8bit() {
let instr = make_instr("bt", vec![Register(Al), Immediate(1)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_bsf_rejects_8bit() {
let instr = make_instr("bsf", vec![Register(Al), Register(Cl)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_bsr_rejects_8bit() {
let instr = make_instr("bsr", vec![Register(Al), Register(Cl)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_popcnt_rejects_8bit() {
let instr = make_instr("popcnt", vec![Register(Al), Register(Cl)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_lzcnt_rejects_8bit() {
let instr = make_instr("lzcnt", vec![Register(Al), Register(Cl)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_tzcnt_rejects_8bit() {
let instr = make_instr("tzcnt", vec![Register(Al), Register(Cl)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_bswap_rejects_8bit() {
let instr = make_instr("bswap", vec![Register(Al)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_bswap_rejects_16bit() {
let instr = make_instr("bswap", vec![Register(Ax)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_push_mem_no_redundant_rex() {
let mem = MemoryOperand {
base: Some(Rdi),
..Default::default()
};
let bytes = encode("push", vec![Memory(Box::new(mem))]);
assert_eq!(bytes, &[0xFF, 0x37], "push [rdi] should not have REX.W");
}
#[test]
fn test_pop_mem_no_redundant_rex() {
let mem = MemoryOperand {
base: Some(Rdi),
..Default::default()
};
let bytes = encode("pop", vec![Memory(Box::new(mem))]);
assert_eq!(bytes, &[0x8F, 0x07], "pop [rdi] should not have REX.W");
}
#[test]
fn test_jmp_mem_no_redundant_rex() {
let mem = MemoryOperand {
base: Some(Rdi),
..Default::default()
};
let bytes = encode("jmp", vec![Memory(Box::new(mem))]);
assert_eq!(bytes, &[0xFF, 0x27], "jmp [rdi] should not have REX.W");
}
#[test]
fn test_call_mem_no_redundant_rex() {
let mem = MemoryOperand {
base: Some(Rdi),
..Default::default()
};
let bytes = encode("call", vec![Memory(Box::new(mem))]);
assert_eq!(bytes, &[0xFF, 0x17], "call [rdi] should not have REX.W");
}
#[test]
fn test_push_mem_extended_needs_rex() {
let mem = MemoryOperand {
base: Some(R15),
..Default::default()
};
let bytes = encode("push", vec![Memory(Box::new(mem))]);
assert_eq!(bytes[0], 0x41, "push [r15] needs REX.B");
assert_eq!(bytes[1], 0xFF);
}
#[test]
fn test_xchg_rbx_rax_short_form() {
let bytes = encode("xchg", vec![Register(Rbx), Register(Rax)]);
assert_eq!(bytes, &[0x48, 0x93], "xchg rbx, rax should use short form");
}
#[test]
fn test_xchg_ecx_eax_short_form() {
let bytes = encode("xchg", vec![Register(Ecx), Register(Eax)]);
assert_eq!(bytes, &[0x91], "xchg ecx, eax should use short form");
}
#[test]
fn test_alu_mem_imm_reloc_explicit() {
let mem = MemoryOperand {
base: Some(Rip),
disp_label: Some(alloc::string::String::from("data")),
addr_mode: AddrMode::Offset,
..Default::default()
};
let mut instr = make_instr("add", vec![Memory(Box::new(mem)), Immediate(5)]);
instr.size_hint = Some(OperandSize::Dword);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert!(
result.relocation.is_some(),
"expected relocation for add [label], imm"
);
let roff = result.relocation.as_ref().unwrap().offset;
assert_eq!(roff, 2, "reloc should point at displacement, not immediate");
}
#[test]
fn test_no_double_segment_override() {
let mem = MemoryOperand {
base: Some(Rbx),
segment: Some(Fs),
..Default::default()
};
let mut instr = make_instr("mov", vec![Register(Rax), Memory(Box::new(mem))]);
instr.prefixes.push(Prefix::SegFs);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
let count_64 = result.bytes.iter().filter(|&&b| b == 0x64).count();
assert_eq!(count_64, 1, "should not emit double FS segment override");
}
#[test]
fn test_rip_relative_trailing_bytes_mov_mem_imm() {
let mem = MemoryOperand {
base: Some(crate::ir::Register::Rip),
index: None,
scale: 1,
disp: 0,
size: Some(OperandSize::Dword),
segment: None,
disp_label: Some(String::from("data")),
addr_mode: AddrMode::Offset,
index_subtract: false,
};
let instr = make_instr_with_hint(
"mov",
vec![Memory(Box::new(mem)), Immediate(42)],
Some(OperandSize::Dword),
);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes[0], 0xC7); assert_eq!(result.bytes[1], 0x05); let reloc = result.relocation.as_ref().unwrap();
assert_eq!(reloc.offset, 2); assert_eq!(reloc.size, 4); assert_eq!(reloc.kind, RelocKind::X86Relative);
assert_eq!(reloc.trailing_bytes, 4); assert_eq!(result.bytes.len(), 10); }
#[test]
fn test_rip_relative_trailing_bytes_mov_mem_reg() {
let mem = MemoryOperand {
base: Some(crate::ir::Register::Rip),
index: None,
scale: 1,
disp: 0,
size: None,
segment: None,
disp_label: Some(String::from("data")),
addr_mode: AddrMode::Offset,
index_subtract: false,
};
let instr = make_instr(
"mov",
vec![
Memory(Box::new(mem)),
Operand::Register(crate::ir::Register::Rax),
],
);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
let reloc = result.relocation.as_ref().unwrap();
assert_eq!(reloc.kind, RelocKind::X86Relative);
assert_eq!(reloc.trailing_bytes, 0); }
#[test]
fn test_rip_relative_trailing_bytes_alu_mem_imm8() {
let mem = MemoryOperand {
base: Some(crate::ir::Register::Rip),
index: None,
scale: 1,
disp: 0,
size: Some(OperandSize::Dword),
segment: None,
disp_label: Some(String::from("target")),
addr_mode: AddrMode::Offset,
index_subtract: false,
};
let instr = make_instr_with_hint(
"add",
vec![Memory(Box::new(mem)), Immediate(5)],
Some(OperandSize::Dword),
);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
let reloc = result.relocation.as_ref().unwrap();
assert_eq!(reloc.kind, RelocKind::X86Relative);
assert_eq!(reloc.trailing_bytes, 1); }
#[test]
fn test_rip_relative_trailing_bytes_jmp() {
let instr = make_instr("jmp", vec![Label(String::from("target"))]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
let reloc = result.relocation.as_ref().unwrap();
assert_eq!(reloc.kind, RelocKind::X86Relative);
assert_eq!(reloc.trailing_bytes, 0);
}
#[test]
fn test_push_imm8_short_form() {
assert_eq!(encode("push", vec![Immediate(0x42)]), vec![0x6A, 0x42]);
}
#[test]
fn test_push_imm32_full_form() {
assert_eq!(
encode("push", vec![Immediate(0x12345678)]),
vec![0x68, 0x78, 0x56, 0x34, 0x12]
);
}
#[test]
fn test_push_imm_out_of_range_rejects() {
let instr = make_instr("push", vec![Immediate(0x1_0000_0000)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_imul_2op_rejects_8bit() {
let instr = make_instr("imul", vec![Register(Al), Register(Bl)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_imul_2op_mem_rejects_8bit() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let instr = make_instr("imul", vec![Register(Al), Memory(Box::new(mem))]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_imul_3op_rejects_8bit() {
let instr = make_instr("imul", vec![Register(Al), Register(Bl), Immediate(5)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_imul_3op_mem_rejects_8bit() {
let mem = MemoryOperand {
base: Some(Rcx),
..Default::default()
};
let instr = make_instr(
"imul",
vec![Register(Al), Memory(Box::new(mem)), Immediate(5)],
);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_cmovcc_reg_mem_rejects_8bit() {
let mem = MemoryOperand {
base: Some(Rbx),
..Default::default()
};
let instr = make_instr("cmove", vec![Register(Al), Memory(Box::new(mem))]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_setcc_rejects_32bit_register() {
let instr = make_instr("sete", vec![Register(Eax)]);
assert!(encode_instruction(&instr, Arch::X86_64).is_err());
}
#[test]
fn test_setcc_accepts_8bit_register() {
assert_eq!(encode("sete", vec![Register(Al)]), vec![0x0F, 0x94, 0xC0]);
}
#[test]
fn test_movzx_mem_word_source_via_mem_size() {
let mem = MemoryOperand {
base: Some(Rbx),
size: Some(OperandSize::Word),
..Default::default()
};
let instr = make_instr("movzx", vec![Register(Eax), Memory(Box::new(mem))]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes, vec![0x0F, 0xB7, 0x03]);
}
#[test]
fn test_movsx_mem_word_source_via_mem_size() {
let mem = MemoryOperand {
base: Some(Rbx),
size: Some(OperandSize::Word),
..Default::default()
};
let instr = make_instr("movsx", vec![Register(Eax), Memory(Box::new(mem))]);
let result = encode_instruction(&instr, Arch::X86_64).unwrap();
assert_eq!(result.bytes, vec![0x0F, 0xBF, 0x03]);
}
#[test]
fn test_16bit_mov_ax_imm16() {
let instr = make_instr("mov", vec![Register(Ax), Immediate(0x1234)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0xB8, 0x34, 0x12]);
}
#[test]
fn test_16bit_mov_eax_imm32() {
let instr = make_instr("mov", vec![Register(Eax), Immediate(0x1234_5678)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x66, 0xB8, 0x78, 0x56, 0x34, 0x12]);
}
#[test]
fn test_16bit_xor_ax_ax() {
let instr = make_instr("xor", vec![Register(Ax), Register(Ax)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x31, 0xC0]);
}
#[test]
fn test_16bit_xor_eax_eax() {
let instr = make_instr("xor", vec![Register(Eax), Register(Eax)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x66, 0x31, 0xC0]);
}
#[test]
fn test_16bit_push_ax() {
let instr = make_instr("push", vec![Register(Ax)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x50]);
}
#[test]
fn test_16bit_push_eax() {
let instr = make_instr("push", vec![Register(Eax)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x66, 0x50]);
}
#[test]
fn test_16bit_pop_bx() {
let instr = make_instr("pop", vec![Register(Bx)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x5B]);
}
#[test]
fn test_16bit_pop_ebx() {
let instr = make_instr("pop", vec![Register(Ebx)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x66, 0x5B]);
}
#[test]
fn test_16bit_inc_cx() {
let instr = make_instr("inc", vec![Register(Cx)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x41]);
}
#[test]
fn test_16bit_inc_ecx() {
let instr = make_instr("inc", vec![Register(Ecx)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x66, 0x41]);
}
#[test]
fn test_16bit_dec_dx() {
let instr = make_instr("dec", vec![Register(Dx)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x4A]);
}
#[test]
fn test_16bit_nop() {
let instr = make_instr("nop", vec![]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x90]);
}
#[test]
fn test_16bit_cli() {
let instr = make_instr("cli", vec![]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0xFA]);
}
#[test]
fn test_16bit_int_10h() {
let instr = make_instr("int", vec![Immediate(0x10)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0xCD, 0x10]);
}
#[test]
fn test_16bit_push_es() {
let instr = make_instr("push", vec![Register(Es)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x06]);
}
#[test]
fn test_16bit_push_cs() {
let instr = make_instr("push", vec![Register(Cs)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x0E]);
}
#[test]
fn test_16bit_pop_ds() {
let instr = make_instr("pop", vec![Register(Ds)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x1F]);
}
#[test]
fn test_16bit_push_imm8() {
let instr = make_instr("push", vec![Immediate(0x42)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x6A, 0x42]);
}
#[test]
fn test_16bit_add_ax_bx() {
let instr = make_instr("add", vec![Register(Ax), Register(Bx)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0x01, 0xD8]);
}
#[test]
fn test_16bit_mov_al_imm8() {
let instr = make_instr("mov", vec![Register(Al), Immediate(0x42)]);
let result = encode_instruction_16(&instr).unwrap();
assert_eq!(result.bytes, vec![0xB0, 0x42]);
}
#[test]
fn test_16bit_rejects_64bit_register() {
let instr = make_instr("mov", vec![Register(Rax), Immediate(1)]);
assert!(encode_instruction_16(&instr).is_err());
}
#[test]
fn test_emit_evex_basic_prefix() {
let mut buf = InstrBytes::new();
emit_evex(
&mut buf, false, false, false, false, 1, false, 1, false, 0, false, 2, false, 0,
);
assert_eq!(buf[0], 0x62, "EVEX escape byte");
assert_eq!(buf[1], 0xF1, "P0");
assert_eq!(buf[2], 0x74, "P1");
assert_eq!(buf[3], 0x48, "P2");
}
#[test]
fn test_emit_evex_w_bit() {
let mut buf = InstrBytes::new();
emit_evex(
&mut buf, false, false, false, false, 1, true, 0, false, 0, false, 2, false, 0,
);
assert_eq!(buf[2], 0xFC, "P1 with W=1");
}
#[test]
fn test_emit_evex_extended_reg() {
let mut buf = InstrBytes::new();
emit_evex(
&mut buf, true, false, false, true, 1, false, 0, false, 0, false, 2, false, 0,
);
assert_eq!(buf[1], 0x61, "P0 with R,R' extended");
}
#[test]
fn test_evex_ll_zmm() {
assert_eq!(evex_ll(Zmm0), 2);
assert_eq!(evex_ll(Zmm31), 2);
}
#[test]
fn test_evex_ll_ymm() {
assert_eq!(evex_ll(Ymm0), 1);
}
#[test]
fn test_evex_ll_xmm() {
assert_eq!(evex_ll(Xmm0), 0);
}
#[test]
fn test_evex_vaddps_zmm0_zmm1_zmm2() {
let bytes = encode(
"vaddps",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x74, 0x48, 0x58, 0xC2]);
}
#[test]
fn test_evex_vaddpd_zmm0_zmm1_zmm2() {
let bytes = encode(
"vaddpd",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0xF5, 0x48, 0x58, 0xC2]);
}
#[test]
fn test_evex_vsubps_zmm3_zmm4_zmm5() {
let bytes = encode(
"vsubps",
vec![Register(Zmm3), Register(Zmm4), Register(Zmm5)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x5C, 0x48, 0x5C, 0xDD]);
}
#[test]
fn test_evex_vmulps_zmm0_zmm1_zmm2() {
let bytes = encode(
"vmulps",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x74, 0x48, 0x59, 0xC2]);
}
#[test]
fn test_evex_vdivps_zmm0_zmm1_zmm2() {
let bytes = encode(
"vdivps",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x74, 0x48, 0x5E, 0xC2]);
}
#[test]
fn test_evex_vmovaps_zmm0_zmm1() {
let bytes = encode("vmovaps", vec![Register(Zmm0), Register(Zmm1)]);
assert_eq!(bytes, vec![0x62, 0xF1, 0x7C, 0x48, 0x28, 0xC1]);
}
#[test]
fn test_evex_vmovdqa32_zmm0_zmm1() {
let bytes = encode("vmovdqa32", vec![Register(Zmm0), Register(Zmm1)]);
assert_eq!(bytes, vec![0x62, 0xF1, 0x7D, 0x48, 0x6F, 0xC1]);
}
#[test]
fn test_evex_vmovdqa64_zmm0_zmm1() {
let bytes = encode("vmovdqa64", vec![Register(Zmm0), Register(Zmm1)]);
assert_eq!(bytes, vec![0x62, 0xF1, 0xFD, 0x48, 0x6F, 0xC1]);
}
#[test]
fn test_evex_vpternlogd_zmm0_zmm1_zmm2_imm() {
let bytes = encode(
"vpternlogd",
vec![
Register(Zmm0),
Register(Zmm1),
Register(Zmm2),
Immediate(0xFF),
],
);
assert_eq!(bytes, vec![0x62, 0xF3, 0x75, 0x48, 0x25, 0xC2, 0xFF]);
}
#[test]
fn test_evex_vpternlogq_zmm0_zmm1_zmm2_imm() {
let bytes = encode(
"vpternlogq",
vec![
Register(Zmm0),
Register(Zmm1),
Register(Zmm2),
Immediate(0xDB),
],
);
assert_eq!(bytes, vec![0x62, 0xF3, 0xF5, 0x48, 0x25, 0xC2, 0xDB]);
}
#[test]
fn test_evex_vpaddd_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpaddd",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xFE, 0xC2]);
}
#[test]
fn test_evex_vpaddq_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpaddq",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0xF5, 0x48, 0xD4, 0xC2]);
}
#[test]
fn test_evex_vpxord_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpxord",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xEF, 0xC2]);
}
#[test]
fn test_evex_vpxorq_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpxorq",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0xF5, 0x48, 0xEF, 0xC2]);
}
#[test]
fn test_evex_vblendmps_zmm0_zmm1_zmm2() {
let bytes = encode(
"vblendmps",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF2, 0x75, 0x48, 0x65, 0xC2]);
}
#[test]
fn test_evex_vpmullq_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpmullq",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF2, 0xF5, 0x48, 0x40, 0xC2]);
}
#[test]
fn test_evex_vaddps_zmm16_zmm17_zmm18() {
let bytes = encode(
"vaddps",
vec![Register(Zmm16), Register(Zmm17), Register(Zmm18)],
);
assert_eq!(bytes, vec![0x62, 0xA1, 0x74, 0x40, 0x58, 0xC2]);
}
#[test]
fn test_evex_vmovaps_zmm31_zmm16() {
let bytes = encode("vmovaps", vec![Register(Zmm31), Register(Zmm16)]);
assert_eq!(bytes, vec![0x62, 0x21, 0x7C, 0x48, 0x28, 0xF8]);
}
#[test]
fn test_evex_vmovdqu8_zmm0_zmm1() {
let bytes = encode("vmovdqu8", vec![Register(Zmm0), Register(Zmm1)]);
assert_eq!(bytes, vec![0x62, 0xF1, 0x7F, 0x48, 0x6F, 0xC1]);
}
#[test]
fn test_evex_vmovdqu16_zmm0_zmm1() {
let bytes = encode("vmovdqu16", vec![Register(Zmm0), Register(Zmm1)]);
assert_eq!(bytes, vec![0x62, 0xF1, 0xFF, 0x48, 0x6F, 0xC1]);
}
#[test]
fn test_evex_vpsravq_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpsravq",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF2, 0xF5, 0x48, 0x46, 0xC2]);
}
#[test]
fn test_evex_vpandd_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpandd",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xDB, 0xC2]);
}
#[test]
fn test_evex_vpord_zmm0_zmm1_zmm2() {
let bytes = encode(
"vpord",
vec![Register(Zmm0), Register(Zmm1), Register(Zmm2)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x75, 0x48, 0xEB, 0xC2]);
}
#[test]
fn test_vex_vaddps_xmm_still_works() {
let bytes = encode(
"vaddps",
vec![Register(Xmm0), Register(Xmm1), Register(Xmm2)],
);
assert_eq!(bytes[0], 0xC5, "should be 2-byte VEX prefix");
}
#[test]
fn test_vex_vaddps_ymm_still_works() {
let bytes = encode(
"vaddps",
vec![Register(Ymm0), Register(Ymm1), Register(Ymm2)],
);
assert_eq!(bytes[0], 0xC5, "should be 2-byte VEX prefix for ymm");
}
#[test]
fn test_evex_vcompressps_zmm0_zmm1() {
let bytes = encode("vcompressps", vec![Register(Zmm0), Register(Zmm1)]);
assert_eq!(bytes, vec![0x62, 0xF2, 0x7D, 0x48, 0x8A, 0xC1]);
}
#[test]
fn test_evex_vexpandps_zmm0_zmm1() {
let bytes = encode("vexpandps", vec![Register(Zmm0), Register(Zmm1)]);
assert_eq!(bytes, vec![0x62, 0xF2, 0x7D, 0x48, 0x88, 0xC1]);
}
#[test]
fn test_evex_vpshufd_zmm0_zmm1_imm() {
let bytes = encode(
"vpshufd",
vec![Register(Zmm0), Register(Zmm1), Immediate(0xE4)],
);
assert_eq!(bytes, vec![0x62, 0xF1, 0x7D, 0x48, 0x70, 0xC1, 0xE4]);
}
}