use core::fmt;
use core::ops::Deref;
use super::error::EncodeError;
use super::flags::FlagEffects;
use super::form::{ATTR_NOT_R0, Form, ModRmSpec, OpcodeField, OperandKind, Slot};
use super::inst::Inst;
use super::operand::{Cond, Mem, Operand};
use super::reg::{Reg, Size};
use super::table;
pub const MAX_INST_LEN: usize = 15;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Encoded {
bytes: [u8; MAX_INST_LEN],
flags: FlagEffects,
form: u16,
len: u8,
disp_at: u8,
imm_at: u8,
}
impl Encoded {
#[inline]
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..usize::from(self.len).min(MAX_INST_LEN)]
}
#[must_use]
pub fn form(&self) -> &'static Form {
table::form(self.form).unwrap_or(&table::FORMS[0])
}
#[inline]
#[must_use]
pub const fn flags(&self) -> FlagEffects {
self.flags
}
pub(crate) const fn rip_disp_at(&self) -> Option<usize> {
if self.disp_at == 0 {
None
} else {
Some(self.disp_at as usize)
}
}
pub(crate) const fn imm_at(&self) -> Option<usize> {
if self.imm_at == 0 {
None
} else {
Some(self.imm_at as usize)
}
}
}
impl Deref for Encoded {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<[u8]> for Encoded {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl fmt::Debug for Encoded {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Encoded(")?;
for (i, b) in self.as_bytes().iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
write!(f, "{b:02x}")?;
}
f.write_str(")")
}
}
pub fn encode(inst: &Inst) -> Result<Encoded, EncodeError> {
check_memory(inst)?;
let (op, cond) = inst.mnemonic().parts();
let mut best = Miss::Kind;
let ops = inst.operands();
for &id in table::by_op(op) {
let Some(form) = table::form(id) else {
continue;
};
if ops.len() != usize::from(form.count)
|| !ops
.iter()
.zip(form.kinds.iter())
.all(|(&o, &k)| kind_fits(k, o))
{
continue;
}
match try_form(form, inst, cond) {
Ok(encoded) => return Ok(encoded),
Err(miss) => best = best.closer(miss),
}
}
Err(best.into_error(inst))
}
pub fn encode_form(form: &Form, inst: &Inst) -> Result<Encoded, EncodeError> {
check_memory(inst)?;
let (op, cond) = inst.mnemonic().parts();
let form = table::form(form.id).unwrap_or(form);
if form.op != op {
return Err(EncodeError::NoForm {
mnemonic: inst.mnemonic(),
});
}
try_form(form, inst, cond).map_err(|miss| miss.into_error(inst))
}
#[derive(Clone, Copy, Debug)]
enum Miss {
Kind,
HighByte,
Imm(i64),
Target(i64),
}
impl Miss {
const fn rank(self) -> u8 {
match self {
Miss::Kind => 0,
Miss::HighByte => 1,
Miss::Imm(_) => 2,
Miss::Target(_) => 3,
}
}
fn closer(self, other: Miss) -> Miss {
if other.rank() > self.rank() {
other
} else {
self
}
}
fn into_error(self, inst: &Inst) -> EncodeError {
match self {
Miss::Kind => EncodeError::NoForm {
mnemonic: inst.mnemonic(),
},
Miss::HighByte => EncodeError::HighByteWithRex,
Miss::Imm(value) => EncodeError::ImmediateOutOfRange { value },
Miss::Target(offset) => EncodeError::TargetOutOfRange { offset },
}
}
}
fn check_memory(inst: &Inst) -> Result<(), EncodeError> {
for op in inst.operands() {
let Operand::Mem(m) = op else { continue };
if let Some(base) = m.base() {
if base.size() != Size::Qword {
return Err(EncodeError::BaseNotQword { base });
}
}
if let Some(index) = m.index() {
if m.is_rip_relative() {
return Err(EncodeError::RipWithIndex);
}
if index.size() != Size::Qword {
return Err(EncodeError::IndexNotQword { index });
}
if index.num() == 4 {
return Err(EncodeError::RspIndex);
}
}
}
Ok(())
}
fn kind_fits(kind: OperandKind, op: Operand) -> bool {
match (kind, op) {
(OperandKind::Reg(size), Operand::Reg(r)) => r.size() == size,
(OperandKind::RegMem(size), Operand::Reg(r)) => r.size() == size,
(OperandKind::RegMem(size) | OperandKind::Mem(size), Operand::Mem(m)) => m.size() == size,
(OperandKind::Fixed(reg), Operand::Reg(r)) => r == reg,
(OperandKind::One, Operand::Imm(v)) => v == 1,
(OperandKind::Imm { .. }, Operand::Imm(_)) => true,
(OperandKind::Rel { .. }, Operand::Rel(_)) => true,
_ => false,
}
}
pub(crate) const fn imm_fits(value: i64, bits: u8, signed: bool) -> bool {
if bits >= 64 {
return signed || value >= 0;
}
if signed {
let half = 1i64 << (bits - 1);
value >= -half && value < half
} else {
value >= 0 && value < (1i64 << bits)
}
}
#[derive(Clone, Copy)]
enum Disp {
None,
Byte(i8),
Dword(i32),
}
impl Disp {
const fn len(self) -> usize {
match self {
Disp::None => 0,
Disp::Byte(_) => 1,
Disp::Dword(_) => 4,
}
}
}
struct Layout<'f> {
form: &'f Form,
rex: u8,
force_rex: bool,
high_byte: bool,
opcode: u8,
reg_field: u8,
md: u8,
rm_field: u8,
sib: Option<u8>,
disp: Disp,
rip: bool,
}
impl<'f> Layout<'f> {
fn new(form: &'f Form) -> Layout<'f> {
Layout {
form,
rex: if form.rex_w { 0b1000 } else { 0 },
force_rex: false,
high_byte: false,
opcode: form.opcode,
reg_field: match form.modrm {
ModRmSpec::Ext(n) => n,
ModRmSpec::Reg | ModRmSpec::Absent => 0,
},
md: 0,
rm_field: 0,
sib: None,
disp: Disp::None,
rip: false,
}
}
fn note_byte_reg(&mut self, r: Reg) {
if r.is_high_byte() {
self.high_byte = true;
} else if r.needs_rex() {
self.force_rex = true;
}
}
fn reg(&mut self, r: Reg) {
self.note_byte_reg(r);
self.reg_field = r.num() & 7;
self.rex |= (r.num() >> 3) << 2;
}
fn opcode_reg(&mut self, r: Reg) {
self.note_byte_reg(r);
self.opcode |= r.num() & 7;
self.rex |= r.num() >> 3;
}
fn rm_reg(&mut self, r: Reg) {
self.note_byte_reg(r);
self.md = 0b11;
self.rm_field = r.num() & 7;
self.rex |= r.num() >> 3;
}
fn rm_mem(&mut self, m: Mem) {
let disp = m.disp();
if m.is_rip_relative() {
self.md = 0b00;
self.rm_field = 0b101;
self.disp = Disp::Dword(disp);
self.rip = true;
return;
}
let index_bits = match m.index() {
Some(index) => {
self.rex |= (index.num() >> 3) << 1;
index.num() & 7
}
None => 0b100,
};
let scale = m.scale().bits() << 6;
match m.base() {
None => {
self.md = 0b00;
self.rm_field = 0b100;
self.sib = Some(scale | (index_bits << 3) | 0b101);
self.disp = Disp::Dword(disp);
}
Some(base) => {
self.rex |= base.num() >> 3;
let low = base.num() & 7;
(self.md, self.disp) = if disp == 0 && low != 0b101 {
(0b00, Disp::None)
} else if let Ok(d) = i8::try_from(disp) {
(0b01, Disp::Byte(d))
} else {
(0b10, Disp::Dword(disp))
};
if m.index().is_some() || low == 0b100 {
self.rm_field = 0b100;
self.sib = Some(scale | (index_bits << 3) | low);
} else {
self.rm_field = low;
}
}
}
}
fn needs_rex(&self) -> bool {
self.rex != 0 || self.force_rex
}
fn prefix_len(&self) -> usize {
let f = self.form;
let modrm = if matches!(f.modrm, ModRmSpec::Absent) {
0
} else {
1 + usize::from(self.sib.is_some()) + self.disp.len()
};
usize::from(f.p66) + usize::from(self.needs_rex()) + usize::from(f.map) + 1 + modrm
}
fn write(&self, imm: i64) -> Encoded {
let f = self.form;
let mut out = Encoded {
bytes: [0; MAX_INST_LEN],
flags: f.flags,
form: f.id,
len: 0,
disp_at: 0,
imm_at: 0,
};
let mut n = 0usize;
let mut put = |byte: u8, n: &mut usize| {
if let Some(slot) = out.bytes.get_mut(*n) {
*slot = byte;
}
*n += 1;
};
if f.p66 {
put(0x66, &mut n);
}
if self.needs_rex() {
put(0x40 | self.rex, &mut n);
}
if f.map == 1 {
put(0x0F, &mut n);
}
put(self.opcode, &mut n);
let mut disp_at = 0;
if !matches!(f.modrm, ModRmSpec::Absent) {
put(
(self.md << 6) | (self.reg_field << 3) | self.rm_field,
&mut n,
);
if let Some(sib) = self.sib {
put(sib, &mut n);
}
if self.rip {
disp_at = n;
}
match self.disp {
Disp::None => {}
Disp::Byte(d) => put(d as u8, &mut n),
Disp::Dword(d) => {
for b in d.to_le_bytes() {
put(b, &mut n);
}
}
}
}
let imm_at = if f.imm_bytes > 0 { n } else { 0 };
for b in imm.to_le_bytes().iter().take(usize::from(f.imm_bytes)) {
put(*b, &mut n);
}
out.len = n.min(MAX_INST_LEN) as u8;
out.disp_at = disp_at as u8;
out.imm_at = imm_at as u8;
out
}
}
fn try_form(form: &Form, inst: &Inst, cond: Option<Cond>) -> Result<Encoded, Miss> {
let ops = inst.operands();
if ops.len() != usize::from(form.count) {
return Err(Miss::Kind);
}
let mut layout = Layout::new(form);
if matches!(form.field, OpcodeField::Cond) {
layout.opcode |= cond.map_or(0, Cond::code);
}
let mut imm = 0i64;
let mut rel: Option<(i64, u8)> = None;
let mut imm_miss = None;
for (i, &op) in ops.iter().enumerate() {
let (Some(&kind), Some(&slot)) = (form.kinds.get(i), form.slots.get(i)) else {
return Err(Miss::Kind);
};
if !kind_fits(kind, op) {
return Err(Miss::Kind);
}
match (slot, op) {
(Slot::Implied, _) => {}
(Slot::Reg, Operand::Reg(r)) => layout.reg(r),
(Slot::Opcode, Operand::Reg(r)) => {
if form.attrs & ATTR_NOT_R0 != 0 && r.num() == 0 {
return Err(Miss::Kind);
}
layout.opcode_reg(r);
}
(Slot::Rm, Operand::Reg(r)) => layout.rm_reg(r),
(Slot::Rm, Operand::Mem(m)) => layout.rm_mem(m),
(Slot::Imm, Operand::Imm(v)) => match kind {
OperandKind::Imm { bits, signed } if imm_fits(v, bits, signed) => imm = v,
_ => imm_miss = Some(v),
},
(Slot::Imm, Operand::Rel(d)) => match kind {
OperandKind::Rel { bits } => rel = Some((d, bits)),
_ => return Err(Miss::Kind),
},
_ => return Err(Miss::Kind),
}
}
if layout.needs_rex() && layout.high_byte {
return Err(Miss::HighByte);
}
if let Some(v) = imm_miss {
return Err(Miss::Imm(v));
}
if let Some((target, bits)) = rel {
let len = (layout.prefix_len() + usize::from(form.imm_bytes)) as i64;
match target.checked_sub(len) {
Some(r) if imm_fits(r, bits, true) => imm = r,
_ => return Err(Miss::Target(target)),
}
}
let mut encoded = layout.write(imm);
if form.reads_cond {
if let Some(c) = cond {
encoded.flags = encoded.flags.with_read(c.reads());
}
}
Ok(encoded)
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::x86_64::{Mnemonic, Scale};
fn enc(m: Mnemonic, ops: &[Operand]) -> Result<alloc::vec::Vec<u8>, EncodeError> {
let inst = Inst::from_slice(m, ops).ok_or(EncodeError::NoForm { mnemonic: m })?;
encode(&inst).map(|e| e.to_vec())
}
#[test]
fn test_imm_fits_boundaries() {
assert!(imm_fits(127, 8, true) && imm_fits(-128, 8, true));
assert!(!imm_fits(128, 8, true) && !imm_fits(-129, 8, true));
assert!(imm_fits(255, 8, false) && !imm_fits(256, 8, false) && !imm_fits(-1, 8, false));
assert!(imm_fits(i64::MIN, 64, true) && imm_fits(i64::MAX, 64, true));
assert!(imm_fits(i32::MIN.into(), 32, true) && !imm_fits(1 << 31, 32, true));
assert!(imm_fits(65535, 16, false) && !imm_fits(65536, 16, false));
}
#[test]
fn test_memory_shapes() {
let mov = |m: Mem| enc(Mnemonic::Mov, &[Reg::EAX.into(), m.into()]);
let d = Size::Dword;
assert_eq!(mov(Mem::new(d, Reg::RAX)), Ok(vec![0x8B, 0x00]));
assert_eq!(mov(Mem::new(d, Reg::RBP)), Ok(vec![0x8B, 0x45, 0x00]));
assert_eq!(mov(Mem::new(d, Reg::R13)), Ok(vec![0x41, 0x8B, 0x45, 0x00]));
assert_eq!(mov(Mem::new(d, Reg::RSP)), Ok(vec![0x8B, 0x04, 0x24]));
assert_eq!(mov(Mem::new(d, Reg::R12)), Ok(vec![0x41, 0x8B, 0x04, 0x24]));
assert_eq!(mov(Mem::rip(d, 16)), Ok(vec![0x8B, 0x05, 0x10, 0, 0, 0]));
assert_eq!(
mov(Mem::absolute(d, 16)),
Ok(vec![0x8B, 0x04, 0x25, 0x10, 0, 0, 0])
);
let idx = Mem::absolute(d, 16).with_index(Reg::RCX, Scale::X4);
assert_eq!(mov(idx), Ok(vec![0x8B, 0x04, 0x8D, 0x10, 0, 0, 0]));
let m = Mem::new(d, Reg::RAX)
.with_index(Reg::R12, Scale::X8)
.with_disp(-128);
assert_eq!(mov(m), Ok(vec![0x42, 0x8B, 0x44, 0xE0, 0x80]));
assert_eq!(
mov(Mem::new(d, Reg::RAX).with_disp(128)),
Ok(vec![0x8B, 0x80, 0x80, 0, 0, 0])
);
}
#[test]
fn test_memory_errors() {
let mov = |m: Mem| enc(Mnemonic::Mov, &[Reg::EAX.into(), m.into()]);
let d = Size::Dword;
assert_eq!(
mov(Mem::new(d, Reg::EAX)),
Err(EncodeError::BaseNotQword { base: Reg::EAX })
);
let bad_index = Mem::new(d, Reg::RAX).with_index(Reg::ECX, Scale::X1);
assert_eq!(
mov(bad_index),
Err(EncodeError::IndexNotQword { index: Reg::ECX })
);
let rsp = Mem::new(d, Reg::RAX).with_index(Reg::RSP, Scale::X1);
assert_eq!(mov(rsp), Err(EncodeError::RspIndex));
let rip = Mem::rip(d, 0).with_index(Reg::RAX, Scale::X2);
assert_eq!(mov(rip), Err(EncodeError::RipWithIndex));
}
#[test]
fn test_high_byte_rules() {
assert_eq!(
enc(Mnemonic::Mov, &[Reg::AH.into(), Reg::BL.into()]),
Ok(vec![0x88, 0xDC])
);
assert_eq!(
enc(Mnemonic::Mov, &[Reg::SIL.into(), Reg::AL.into()]),
Ok(vec![0x40, 0x88, 0xC6])
);
assert_eq!(
enc(Mnemonic::Mov, &[Reg::AH.into(), Reg::SIL.into()]),
Err(EncodeError::HighByteWithRex)
);
assert_eq!(
enc(Mnemonic::Movzx, &[Reg::RAX.into(), Reg::AH.into()]),
Err(EncodeError::HighByteWithRex)
);
assert_eq!(
enc(Mnemonic::Movzx, &[Reg::EAX.into(), Reg::AH.into()]),
Ok(vec![0x0F, 0xB6, 0xC4])
);
}
#[test]
fn test_branch_selection() {
let jmp = |d| enc(Mnemonic::Jmp, &[Operand::Rel(d)]);
assert_eq!(jmp(2), Ok(vec![0xEB, 0x00]));
assert_eq!(jmp(129), Ok(vec![0xEB, 0x7F]));
assert_eq!(jmp(130), Ok(vec![0xE9, 0x7D, 0, 0, 0]));
assert_eq!(jmp(-126), Ok(vec![0xEB, 0x80]));
assert_eq!(jmp(-127), Ok(vec![0xE9, 0x7C, 0xFF, 0xFF, 0xFF]));
let far = i64::from(i32::MAX) + 6;
assert_eq!(jmp(far), Err(EncodeError::TargetOutOfRange { offset: far }));
assert_eq!(
jmp(i64::MIN),
Err(EncodeError::TargetOutOfRange { offset: i64::MIN })
);
}
#[test]
fn test_not_r0_avoids_nop() {
assert_eq!(
enc(Mnemonic::Xchg, &[Reg::EBX.into(), Reg::EAX.into()]),
Ok(vec![0x93])
);
assert_eq!(
enc(Mnemonic::Xchg, &[Reg::EAX.into(), Reg::EAX.into()]),
Ok(vec![0x87, 0xC0])
);
assert_eq!(
enc(Mnemonic::Xchg, &[Reg::RAX.into(), Reg::RAX.into()]),
Ok(vec![0x48, 0x87, 0xC0])
);
assert_eq!(
enc(Mnemonic::Xchg, &[Reg::R8.into(), Reg::RAX.into()]),
Ok(vec![0x49, 0x90])
);
}
#[test]
fn test_closest_miss_wins() {
assert_eq!(
enc(Mnemonic::Shl, &[Reg::EAX.into(), Operand::Imm(256)]),
Err(EncodeError::ImmediateOutOfRange { value: 256 })
);
assert_eq!(
enc(Mnemonic::Ret, &[Reg::RAX.into()]),
Err(EncodeError::NoForm {
mnemonic: Mnemonic::Ret
})
);
}
#[test]
fn test_encode_form_rejects_other_mnemonic() {
let add = table::by_op(crate::x86_64::inst::Op::Add)[0];
let form = table::form(add).copied();
let inst = Inst::new(Mnemonic::Sub, [Reg::AL.into(), Operand::Imm(1)]);
assert_eq!(
form.map(|f| encode_form(&f, &inst)),
Some(Err(EncodeError::NoForm {
mnemonic: Mnemonic::Sub
}))
);
}
}