use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CondCode {
Equal,
NotEqual,
Less,
LessEq,
Greater,
GreaterEq,
Overflow,
AboveEq,
Below,
}
impl CondCode {
pub(crate) fn setcc_byte(self) -> u8 {
match self {
Self::Equal => 0x94,
Self::NotEqual => 0x95,
Self::Less => 0x9C,
Self::LessEq => 0x9E,
Self::Greater => 0x9F,
Self::GreaterEq => 0x9D,
Self::Overflow => 0x90,
Self::AboveEq => 0x93,
Self::Below => 0x92,
}
}
pub(crate) fn jcc_byte(self) -> u8 {
match self {
Self::Equal => 0x84,
Self::NotEqual => 0x85,
Self::Less => 0x8C,
Self::LessEq => 0x8E,
Self::Greater => 0x8F,
Self::GreaterEq => 0x8D,
Self::Overflow => 0x80,
Self::AboveEq => 0x83,
Self::Below => 0x82,
}
}
pub fn invert(self) -> Self {
match self {
Self::Equal => Self::NotEqual,
Self::NotEqual => Self::Equal,
Self::Less => Self::GreaterEq,
Self::LessEq => Self::Greater,
Self::Greater => Self::LessEq,
Self::GreaterEq => Self::Less,
Self::Overflow => Self::Overflow, Self::AboveEq => Self::Below,
Self::Below => Self::AboveEq,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Reg64 {
Rax = 0,
Rcx = 1,
Rdx = 2,
Rbx = 3,
Rsp = 4,
Rbp = 5,
Rsi = 6,
Rdi = 7,
R8 = 8,
R9 = 9,
R10 = 10,
R11 = 11,
R12 = 12,
R13 = 13,
R14 = 14,
R15 = 15,
}
impl Reg64 {
#[inline]
pub(crate) fn enc(self) -> u8 {
(self as u8) & 0x07
}
#[inline]
pub(crate) fn needs_rex(self) -> bool {
(self as u8) >= 8
}
}
impl fmt::Display for Reg64 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::Rax => "rax",
Self::Rcx => "rcx",
Self::Rdx => "rdx",
Self::Rbx => "rbx",
Self::Rsp => "rsp",
Self::Rbp => "rbp",
Self::Rsi => "rsi",
Self::Rdi => "rdi",
Self::R8 => "r8",
Self::R9 => "r9",
Self::R10 => "r10",
Self::R11 => "r11",
Self::R12 => "r12",
Self::R13 => "r13",
Self::R14 => "r14",
Self::R15 => "r15",
};
f.write_str(name)
}
}
#[derive(Debug, Default)]
pub struct Label {
bound_offset: Option<usize>,
patch_sites: Vec<usize>,
}
impl Label {
pub fn new() -> Self {
Self::default()
}
pub fn bound_offset(&self) -> Option<usize> {
self.bound_offset
}
pub fn is_bound(&self) -> bool {
self.bound_offset.is_some()
}
}
#[derive(Debug, Default)]
pub struct MacroAssembler {
buf: Vec<u8>,
}
impl MacroAssembler {
pub fn new() -> Self {
Self::default()
}
pub fn code(&self) -> &[u8] {
&self.buf
}
pub fn into_code(self) -> Vec<u8> {
self.buf
}
pub fn position(&self) -> usize {
self.buf.len()
}
pub fn align_to(&mut self, boundary: usize) {
debug_assert!(boundary.is_power_of_two());
let pos = self.buf.len();
let remainder = pos & (boundary - 1);
if remainder == 0 {
return;
}
let pad = boundary - remainder;
self.emit_nop_sequence(pad);
}
fn emit_nop_sequence(&mut self, mut n: usize) {
static NOPS: [&[u8]; 10] = [
&[],
&[0x90],
&[0x66, 0x90],
&[0x0F, 0x1F, 0x00],
&[0x0F, 0x1F, 0x40, 0x00],
&[0x0F, 0x1F, 0x44, 0x00, 0x00],
&[0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00],
&[0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00],
&[0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
&[0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
];
while n > 0 {
let chunk = n.min(9);
self.buf.extend_from_slice(NOPS[chunk]);
n -= chunk;
}
}
pub fn bind_label(&mut self, label: &mut Label) {
assert!(label.bound_offset.is_none(), "label already bound");
let here = self.buf.len();
for site in &label.patch_sites {
let rel32 = (here as i32) - ((*site as i32) + 4);
self.buf[*site..*site + 4].copy_from_slice(&rel32.to_le_bytes());
}
label.patch_sites.clear();
label.bound_offset = Some(here);
}
fn emit_rex_wrb(&mut self, reg_field: Reg64, rm_field: Reg64) {
let r_bit = if reg_field.needs_rex() { 0x04 } else { 0 };
let b_bit = if rm_field.needs_rex() { 0x01 } else { 0 };
self.buf.push(0x48 | r_bit | b_bit);
}
fn emit_rex_rb_if_needed(&mut self, reg_field: Reg64, rm_field: Reg64) {
let r_bit = if reg_field.needs_rex() { 0x04 } else { 0 };
let b_bit = if rm_field.needs_rex() { 0x01 } else { 0 };
if r_bit | b_bit != 0 {
self.buf.push(0x40 | r_bit | b_bit);
}
}
fn emit_rex_b_only(&mut self, reg: Reg64) {
if reg.needs_rex() {
self.buf.push(0x41); }
}
fn emit_modrm_rr(&mut self, reg_field: Reg64, rm_field: Reg64) {
self.buf
.push(0xC0 | (reg_field.enc() << 3) | rm_field.enc());
}
fn emit_modrm_digit(&mut self, digit: u8, rm_field: Reg64) {
self.buf.push(0xC0 | (digit << 3) | rm_field.enc());
}
pub fn lea_scaled(&mut self, dst: Reg64, base: Reg64, index: Reg64, scale: u8) {
debug_assert!(
matches!(scale, 1 | 2 | 4 | 8),
"LEA scale must be 1, 2, 4, or 8"
);
let ss = match scale {
1 => 0b00,
2 => 0b01,
4 => 0b10,
8 => 0b11,
_ => unreachable!(),
};
let r_bit = if dst.needs_rex() { 0x04 } else { 0 };
let x_bit = if index.needs_rex() { 0x02 } else { 0 };
let b_bit = if base.needs_rex() { 0x01 } else { 0 };
self.buf.push(0x48 | r_bit | x_bit | b_bit);
self.buf.push(0x8D); self.buf.push((dst.enc() << 3) | 0x04);
self.buf.push((ss << 6) | (index.enc() << 3) | base.enc());
}
pub fn lea_scaled_disp32(
&mut self,
dst: Reg64,
base: Reg64,
index: Reg64,
scale: u8,
disp: i32,
) {
debug_assert!(
matches!(scale, 1 | 2 | 4 | 8),
"LEA scale must be 1, 2, 4, or 8"
);
let ss = match scale {
1 => 0b00,
2 => 0b01,
4 => 0b10,
8 => 0b11,
_ => unreachable!(),
};
let r_bit = if dst.needs_rex() { 0x04 } else { 0 };
let x_bit = if index.needs_rex() { 0x02 } else { 0 };
let b_bit = if base.needs_rex() { 0x01 } else { 0 };
self.buf.push(0x48 | r_bit | x_bit | b_bit);
self.buf.push(0x8D);
self.buf.push(0x80 | (dst.enc() << 3) | 0x04);
self.buf.push((ss << 6) | (index.enc() << 3) | base.enc());
self.emit_i32(disp);
}
fn emit_i32(&mut self, v: i32) {
self.buf.extend_from_slice(&v.to_le_bytes());
}
fn emit_i64(&mut self, v: i64) {
self.buf.extend_from_slice(&v.to_le_bytes());
}
fn emit_rel32_for_label(&mut self, label: &mut Label) {
let rel32_start = self.buf.len();
if let Some(target) = label.bound_offset {
let rel32 = (target as i32) - ((rel32_start as i32) + 4);
self.emit_i32(rel32);
} else {
self.buf.extend_from_slice(&[0u8; 4]);
label.patch_sites.push(rel32_start);
}
}
pub fn mov_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x8B);
self.emit_modrm_rr(dst, src);
}
pub fn mov_ri(&mut self, dst: Reg64, imm: i64) {
if (i32::MIN as i64..=i32::MAX as i64).contains(&imm) {
self.emit_rex_wrb(Reg64::Rax, dst);
self.buf.push(0xC7);
self.emit_modrm_digit(0, dst);
self.emit_i32(imm as i32);
} else {
let b_bit = if dst.needs_rex() { 0x01 } else { 0 };
self.buf.push(0x48 | b_bit);
self.buf.push(0xB8 | dst.enc());
self.emit_i64(imm);
}
}
pub fn mov_ri32(&mut self, dst: Reg64, imm: u32) {
self.emit_rex_b_only(dst);
self.buf.push(0xB8 | dst.enc());
self.buf.extend_from_slice(&imm.to_le_bytes());
}
pub fn lea_rip_rel(&mut self, dst: Reg64, offset: i32) {
let r_bit = if dst.needs_rex() { 0x04 } else { 0 };
self.buf.push(0x48 | r_bit); self.buf.push(0x8D);
self.buf.push((dst.enc() << 3) | 0x05);
self.emit_i32(offset);
}
pub fn add_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x03);
self.emit_modrm_rr(dst, src);
}
pub fn add_ri(&mut self, dst: Reg64, imm: i32) {
self.emit_rex_wrb(Reg64::Rax, dst);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(0, dst);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(0, dst);
self.emit_i32(imm);
}
}
pub fn sub_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x2B);
self.emit_modrm_rr(dst, src);
}
pub fn sub_ri(&mut self, dst: Reg64, imm: i32) {
self.emit_rex_wrb(Reg64::Rax, dst);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(5, dst);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(5, dst);
self.emit_i32(imm);
}
}
pub fn add32_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_rb_if_needed(dst, src);
self.buf.push(0x03);
self.emit_modrm_rr(dst, src);
}
pub fn add32_ri(&mut self, dst: Reg64, imm: i32) {
self.emit_rex_rb_if_needed(Reg64::Rax, dst);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(0, dst);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(0, dst);
self.emit_i32(imm);
}
}
pub fn sub32_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_rb_if_needed(dst, src);
self.buf.push(0x2B);
self.emit_modrm_rr(dst, src);
}
pub fn sub32_ri(&mut self, dst: Reg64, imm: i32) {
self.emit_rex_rb_if_needed(Reg64::Rax, dst);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(5, dst);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(5, dst);
self.emit_i32(imm);
}
}
pub fn imul32_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_rb_if_needed(dst, src);
self.buf.push(0x0F);
self.buf.push(0xAF);
self.emit_modrm_rr(dst, src);
}
pub fn imul32_rri(&mut self, dst: Reg64, src: Reg64, imm: i32) {
self.emit_rex_rb_if_needed(dst, src);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x6B);
self.emit_modrm_rr(dst, src);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x69);
self.emit_modrm_rr(dst, src);
self.emit_i32(imm);
}
}
pub fn movsxd_sign_extend(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x63);
self.emit_modrm_rr(dst, src);
}
pub fn cmp_rr(&mut self, lhs: Reg64, rhs: Reg64) {
self.emit_rex_wrb(lhs, rhs);
self.buf.push(0x3B);
self.emit_modrm_rr(lhs, rhs);
}
pub fn cmp32_rr(&mut self, lhs: Reg64, rhs: Reg64) {
self.emit_rex_rb_if_needed(lhs, rhs);
self.buf.push(0x3B);
self.emit_modrm_rr(lhs, rhs);
}
pub fn cmp_ri(&mut self, reg: Reg64, imm: i32) {
self.emit_rex_wrb(Reg64::Rax, reg);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(7, reg);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(7, reg);
self.emit_i32(imm);
}
}
pub fn cmp32_ri(&mut self, reg: Reg64, imm: i32) {
self.emit_rex_rb_if_needed(Reg64::Rax, reg);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(7, reg);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(7, reg);
self.emit_i32(imm);
}
}
pub fn cmp_rm(&mut self, reg: Reg64, base: Reg64, disp: i32) {
self.emit_rex_wrb(reg, base);
self.buf.push(0x3B);
self.emit_modrm_base_disp32(reg, base, disp);
}
pub fn cmp_mi(&mut self, base: Reg64, disp: i32, imm: i32) {
self.emit_rex_wrb(Reg64::Rdi, base);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_base_disp32(Reg64::Rdi, base, disp);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_base_disp32(Reg64::Rdi, base, disp);
self.emit_i32(imm);
}
}
pub fn movsxd_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x63);
self.emit_modrm_rr(dst, src);
}
pub fn movsxd_base_disp32(&mut self, dst: Reg64, base: Reg64, disp: i32) {
self.emit_rex_wrb(dst, base);
self.buf.push(0x63);
self.emit_modrm_base_disp32(dst, base, disp);
}
pub fn jmp(&mut self, label: &mut Label) {
self.buf.push(0xE9);
self.emit_rel32_for_label(label);
}
pub fn jmp_reg(&mut self, reg: Reg64) {
self.emit_rex_b_only(reg);
self.buf.push(0xFF);
self.emit_modrm_digit(4, reg);
}
pub fn je(&mut self, label: &mut Label) {
self.buf.push(0x0F);
self.buf.push(0x84);
self.emit_rel32_for_label(label);
}
pub fn jne(&mut self, label: &mut Label) {
self.buf.push(0x0F);
self.buf.push(0x85);
self.emit_rel32_for_label(label);
}
pub fn jo(&mut self, label: &mut Label) {
self.buf.push(0x0F);
self.buf.push(0x80);
self.emit_rel32_for_label(label);
}
pub fn call_reg(&mut self, reg: Reg64) {
self.emit_rex_b_only(reg);
self.buf.push(0xFF);
self.emit_modrm_digit(2, reg);
}
pub fn call_rel(&mut self, label: &mut Label) {
self.buf.push(0xE8);
self.emit_rel32_for_label(label);
}
pub fn ret(&mut self) {
self.buf.push(0xC3);
}
pub fn push(&mut self, reg: Reg64) {
self.emit_rex_b_only(reg);
self.buf.push(0x50 | reg.enc());
}
pub fn pop(&mut self, reg: Reg64) {
self.emit_rex_b_only(reg);
self.buf.push(0x58 | reg.enc());
}
pub fn xor_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x33);
self.emit_modrm_rr(dst, src);
}
pub fn xor_ri(&mut self, dst: Reg64, imm: i32) {
self.emit_rex_wrb(Reg64::Rax, dst);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(6, dst);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(6, dst);
self.emit_i32(imm);
}
}
pub fn or_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x0B);
self.emit_modrm_rr(dst, src);
}
pub fn or_ri(&mut self, dst: Reg64, imm: i32) {
self.emit_rex_wrb(Reg64::Rax, dst);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(1, dst);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(1, dst);
self.emit_i32(imm);
}
}
pub fn and_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x23);
self.emit_modrm_rr(dst, src);
}
pub fn and_ri(&mut self, dst: Reg64, imm: i32) {
self.emit_rex_wrb(Reg64::Rax, dst);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x83);
self.emit_modrm_digit(4, dst);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x81);
self.emit_modrm_digit(4, dst);
self.emit_i32(imm);
}
}
pub fn not_r(&mut self, dst: Reg64) {
self.emit_rex_wrb(Reg64::Rax, dst);
self.buf.push(0xF7);
self.emit_modrm_digit(2, dst);
}
pub fn imul_rr(&mut self, dst: Reg64, src: Reg64) {
self.emit_rex_wrb(dst, src);
self.buf.push(0x0F);
self.buf.push(0xAF);
self.emit_modrm_rr(dst, src);
}
pub fn imul_rri(&mut self, dst: Reg64, src: Reg64, imm: i32) {
self.emit_rex_wrb(dst, src);
if (i8::MIN as i32..=i8::MAX as i32).contains(&imm) {
self.buf.push(0x6B);
self.emit_modrm_rr(dst, src);
self.buf.push(imm as i8 as u8);
} else {
self.buf.push(0x69);
self.emit_modrm_rr(dst, src);
self.emit_i32(imm);
}
}
pub fn neg_r(&mut self, dst: Reg64) {
self.emit_rex_wrb(Reg64::Rax, dst);
self.buf.push(0xF7);
self.emit_modrm_digit(3, dst);
}
pub fn cdq(&mut self) {
self.buf.push(0x99);
}
pub fn idiv32_r(&mut self, src: Reg64) {
self.emit_rex_rb_if_needed(Reg64::Rax, src);
self.buf.push(0xF7);
self.emit_modrm_digit(7, src);
}
pub fn test_rr(&mut self, lhs: Reg64, rhs: Reg64) {
self.emit_rex_wrb(rhs, lhs);
self.buf.push(0x85);
self.emit_modrm_rr(rhs, lhs);
}
fn emit_modrm_base_disp32(&mut self, reg_field: Reg64, base: Reg64, disp: i32) {
if base.enc() == 4 {
self.buf.push(0x80 | (reg_field.enc() << 3) | 4);
self.buf.push(0x24); } else {
self.buf.push(0x80 | (reg_field.enc() << 3) | base.enc());
}
self.emit_i32(disp);
}
pub fn mov_load_base_disp32(&mut self, dst: Reg64, base: Reg64, disp: i32) {
self.emit_rex_wrb(dst, base);
self.buf.push(0x8B);
self.emit_modrm_base_disp32(dst, base, disp);
}
pub fn lea_base_disp32(&mut self, dst: Reg64, base: Reg64, disp: i32) {
self.emit_rex_wrb(dst, base);
self.buf.push(0x8D);
self.emit_modrm_base_disp32(dst, base, disp);
}
pub fn mov_store_base_disp32(&mut self, base: Reg64, disp: i32, src: Reg64) {
self.emit_rex_wrb(src, base);
self.buf.push(0x89);
self.emit_modrm_base_disp32(src, base, disp);
}
pub fn mov_store_dword_base_disp32(&mut self, base: Reg64, disp: i32, src: Reg64) {
self.emit_rex_rb_if_needed(src, base);
self.buf.push(0x89);
self.emit_modrm_base_disp32(src, base, disp);
}
pub fn mov_store_byte_base_disp32(&mut self, base: Reg64, disp: i32, src: Reg64) {
let r = if src.needs_rex() { 4u8 } else { 0 };
let b = if base.needs_rex() { 1u8 } else { 0 };
let force = if src.enc() >= 4 || base.enc() >= 4 {
0x40u8
} else {
0
};
let rex = 0x40 | r | b;
if rex != 0x40 || force != 0 {
self.buf.push(rex);
}
self.buf.push(0x88); self.emit_modrm_base_disp32(src, base, disp);
}
pub fn mov_store_byte_imm_base_disp32(&mut self, base: Reg64, disp: i32, imm: u8) {
if base.needs_rex() {
self.buf.push(0x41); }
self.buf.push(0xC6); self.emit_modrm_base_disp32(Reg64::Rax, base, disp); self.buf.push(imm);
}
pub fn inc_mem_base_disp32(&mut self, base: Reg64, disp: i32) {
let rex = 0x48 | if base.needs_rex() { 1u8 } else { 0 };
self.buf.push(rex);
self.buf.push(0xFF); self.emit_modrm_base_disp32(Reg64::Rax, base, disp);
}
pub fn setcc_al(&mut self, cc: CondCode) {
self.buf.push(0x0F);
self.buf.push(cc.setcc_byte());
self.buf.push(0xC0);
}
pub fn movzx_r64_al(&mut self, dst: Reg64) {
let r_bit = if dst.needs_rex() { 0x04 } else { 0 };
self.buf.push(0x48 | r_bit); self.buf.push(0x0F);
self.buf.push(0xB6);
self.buf.push(0xC0 | (dst.enc() << 3));
}
pub fn movzx_byte_base_disp32(&mut self, dst: Reg64, base: Reg64, disp: i32) {
self.emit_rex_wrb(dst, base);
self.buf.push(0x0F);
self.buf.push(0xB6);
self.emit_modrm_base_disp32(dst, base, disp);
}
pub fn jcc(&mut self, cc: CondCode, label: &mut Label) {
self.buf.push(0x0F);
self.buf.push(cc.jcc_byte());
self.emit_rel32_for_label(label);
}
pub fn rep_stosq(&mut self) {
self.buf.push(0xF3); self.buf.push(0x48); self.buf.push(0xAB); }
pub(crate) fn emit_byte(&mut self, b: u8) {
self.buf.push(b);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ret_encoding() {
let mut m = MacroAssembler::new();
m.ret();
assert_eq!(m.code(), &[0xC3]);
}
#[test]
fn test_push_classic_registers() {
let mut m = MacroAssembler::new();
m.push(Reg64::Rax);
m.push(Reg64::Rdi);
assert_eq!(m.code(), &[0x50, 0x57]);
}
#[test]
fn test_push_extended_registers() {
let mut m = MacroAssembler::new();
m.push(Reg64::R8);
m.push(Reg64::R15);
assert_eq!(m.code(), &[0x41, 0x50, 0x41, 0x57]);
}
#[test]
fn test_pop_encoding() {
let mut m = MacroAssembler::new();
m.pop(Reg64::Rax);
m.pop(Reg64::R15);
assert_eq!(m.code(), &[0x58, 0x41, 0x5F]);
}
#[test]
fn test_mov_rr_encoding() {
let mut m = MacroAssembler::new();
m.mov_rr(Reg64::Rax, Reg64::Rdi);
assert_eq!(m.code(), &[0x48, 0x8B, 0xC7]);
}
#[test]
fn test_mov_rr_extended_src() {
let mut m = MacroAssembler::new();
m.mov_rr(Reg64::Rax, Reg64::R9);
assert_eq!(m.code(), &[0x49, 0x8B, 0xC1]);
}
#[test]
fn test_mov_rr_both_extended() {
let mut m = MacroAssembler::new();
m.mov_rr(Reg64::R11, Reg64::R9);
assert_eq!(m.code(), &[0x4D, 0x8B, 0xD9]);
}
#[test]
fn test_mov_ri_small_imm() {
let mut m = MacroAssembler::new();
m.mov_ri(Reg64::Rdi, 42);
assert_eq!(m.code(), &[0x48, 0xC7, 0xC7, 0x2A, 0x00, 0x00, 0x00]);
}
#[test]
fn test_mov_ri_large_imm() {
let mut m = MacroAssembler::new();
m.mov_ri(Reg64::Rax, 0x1_2345_6789_i64);
assert_eq!(
m.code(),
&[0x48, 0xB8, 0x89, 0x67, 0x45, 0x23, 0x01, 0x00, 0x00, 0x00]
);
}
#[test]
fn test_mov_ri_extended_reg_large_imm() {
let mut m = MacroAssembler::new();
m.mov_ri(Reg64::R9, 0x1_2345_6789_i64);
assert_eq!(
m.code(),
&[0x49, 0xB9, 0x89, 0x67, 0x45, 0x23, 0x01, 0x00, 0x00, 0x00]
);
}
#[test]
fn test_add_rr_encoding() {
let mut m = MacroAssembler::new();
m.add_rr(Reg64::Rax, Reg64::Rsi);
assert_eq!(m.code(), &[0x48, 0x03, 0xC6]);
}
#[test]
fn test_add_ri_short_form() {
let mut m = MacroAssembler::new();
m.add_ri(Reg64::Rax, 5);
assert_eq!(m.code(), &[0x48, 0x83, 0xC0, 0x05]);
}
#[test]
fn test_add_ri_long_form() {
let mut m = MacroAssembler::new();
m.add_ri(Reg64::Rax, 1000);
assert_eq!(m.code(), &[0x48, 0x81, 0xC0, 0xE8, 0x03, 0x00, 0x00]);
}
#[test]
fn test_sub_rr_encoding() {
let mut m = MacroAssembler::new();
m.sub_rr(Reg64::Rax, Reg64::Rsi);
assert_eq!(m.code(), &[0x48, 0x2B, 0xC6]);
}
#[test]
fn test_sub_ri_short_form() {
let mut m = MacroAssembler::new();
m.sub_ri(Reg64::Rsp, 32);
assert_eq!(m.code(), &[0x48, 0x83, 0xEC, 0x20]);
}
#[test]
fn test_cmp_rr_encoding() {
let mut m = MacroAssembler::new();
m.cmp_rr(Reg64::Rax, Reg64::Rcx);
assert_eq!(m.code(), &[0x48, 0x3B, 0xC1]);
}
#[test]
fn test_cmp_ri_short_form() {
let mut m = MacroAssembler::new();
m.cmp_ri(Reg64::Rcx, 0);
assert_eq!(m.code(), &[0x48, 0x83, 0xF9, 0x00]);
}
#[test]
fn test_jmp_reg_encoding() {
let mut m = MacroAssembler::new();
m.jmp_reg(Reg64::Rax);
m.jmp_reg(Reg64::R8);
assert_eq!(m.code(), &[0xFF, 0xE0, 0x41, 0xFF, 0xE0]);
}
#[test]
fn test_call_reg_encoding() {
let mut m = MacroAssembler::new();
m.call_reg(Reg64::Rax);
assert_eq!(m.code(), &[0xFF, 0xD0]);
}
#[test]
fn test_rep_stosq_encoding() {
let mut m = MacroAssembler::new();
m.rep_stosq();
assert_eq!(m.code(), &[0xF3, 0x48, 0xAB]);
}
#[test]
fn test_lea_rip_rel_encoding() {
let mut m = MacroAssembler::new();
m.lea_rip_rel(Reg64::Rax, 0);
assert_eq!(m.code(), &[0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00]);
}
#[test]
fn test_lea_scaled_times3() {
let mut m = MacroAssembler::new();
m.lea_scaled(Reg64::Rax, Reg64::Rcx, Reg64::Rcx, 2);
assert_eq!(m.code(), &[0x48, 0x8D, 0x04, 0x49]);
}
#[test]
fn test_lea_scaled_times5() {
let mut m = MacroAssembler::new();
m.lea_scaled(Reg64::Rdx, Reg64::Rsi, Reg64::Rsi, 4);
assert_eq!(m.code(), &[0x48, 0x8D, 0x14, 0xB6]);
}
#[test]
fn test_lea_scaled_with_extended_regs() {
let mut m = MacroAssembler::new();
m.lea_scaled(Reg64::R8, Reg64::R9, Reg64::R9, 8);
assert_eq!(m.code(), &[0x4F, 0x8D, 0x04, 0xC9]);
}
#[test]
fn test_forward_jmp_patched_correctly() {
let mut m = MacroAssembler::new();
let mut done = Label::new();
m.jmp(&mut done);
m.mov_ri(Reg64::Rax, 1);
m.bind_label(&mut done);
m.ret();
let code = m.code();
assert_eq!(code[0], 0xE9);
let rel32 = i32::from_le_bytes([code[1], code[2], code[3], code[4]]);
assert_eq!(rel32, 7);
assert!(done.is_bound());
assert_eq!(done.bound_offset(), Some(12));
}
#[test]
fn test_backward_jne_loop() {
let mut m = MacroAssembler::new();
let mut loop_top = Label::new();
m.bind_label(&mut loop_top); m.sub_ri(Reg64::Rcx, 1); m.jne(&mut loop_top);
let code = m.code();
assert_eq!(&code[0..2], &[0x48, 0x83]); assert_eq!(code[4], 0x0F);
assert_eq!(code[5], 0x85);
let rel32 = i32::from_le_bytes([code[6], code[7], code[8], code[9]]);
assert_eq!(rel32, -10);
}
#[test]
fn test_forward_je_patched_correctly() {
let mut m = MacroAssembler::new();
let mut target = Label::new();
m.je(&mut target); m.ret(); m.bind_label(&mut target);
let code = m.code();
let rel32 = i32::from_le_bytes([code[2], code[3], code[4], code[5]]);
assert_eq!(rel32, 1);
}
#[test]
fn test_call_rel_forward_patched() {
let mut m = MacroAssembler::new();
let mut fn_label = Label::new();
m.call_rel(&mut fn_label);
m.ret();
m.bind_label(&mut fn_label);
m.ret();
let code = m.code();
assert_eq!(code[0], 0xE8);
let rel32 = i32::from_le_bytes([code[1], code[2], code[3], code[4]]);
assert_eq!(rel32, 1);
}
#[test]
fn test_emit_add_function_bytes() {
let mut m = MacroAssembler::new();
m.mov_rr(Reg64::Rax, Reg64::Rdi);
m.add_rr(Reg64::Rax, Reg64::Rsi);
m.ret();
assert_eq!(m.code(), &[0x48, 0x8B, 0xC7, 0x48, 0x03, 0xC6, 0xC3]);
}
#[test]
fn test_reg64_display() {
assert_eq!(Reg64::Rax.to_string(), "rax");
assert_eq!(Reg64::Rdi.to_string(), "rdi");
assert_eq!(Reg64::R8.to_string(), "r8");
assert_eq!(Reg64::R15.to_string(), "r15");
}
#[test]
fn test_position_tracks_offset() {
let mut m = MacroAssembler::new();
assert_eq!(m.position(), 0);
m.ret();
assert_eq!(m.position(), 1);
m.push(Reg64::Rax);
assert_eq!(m.position(), 2);
}
#[cfg(target_arch = "x86_64")]
#[test]
fn test_emit_add_and_call_via_ffi() {
use crate::executable_memory::ExecutableMemory;
let mut masm = MacroAssembler::new();
masm.mov_rr(Reg64::Rax, Reg64::Rdi);
masm.add_rr(Reg64::Rax, Reg64::Rsi);
masm.ret();
let code = masm.into_code();
let mem = ExecutableMemory::new(&code).expect("executable allocation must succeed");
#[cfg(unix)]
unsafe {
let f: extern "C" fn(i64, i64) -> i64 = std::mem::transmute(mem.as_ptr());
assert_eq!(f(3, 4), 7);
assert_eq!(f(-1, 1), 0);
assert_eq!(f(100, -50), 50);
assert_eq!(f(0, 0), 0);
assert_eq!(f(i64::MAX, 0), i64::MAX);
}
#[cfg(not(unix))]
{
assert_eq!(mem.len(), code.len());
}
}
}