use std::fmt;
use crate::regs::PhysReg;
use crate::x86_64::text::{Arg, Width};
use Fits::{Signed8, Signed32};
use Size::{Byte, Long, Quad, Word};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
Reg,
Mem,
Imm,
Dest,
}
impl Kind {
#[must_use]
pub fn of(arg: Arg) -> Self {
match arg {
Arg::Reg(_, _) | Arg::Named(_) => Kind::Reg,
Arg::Mem => Kind::Mem,
Arg::Imm => Kind::Imm,
Arg::Symbol | Arg::Label => Kind::Dest,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Size {
Byte,
Word,
Long,
Quad,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fields {
None,
Ext {
rm: u8,
ext: u8,
},
Pair {
rm: u8,
reg: u8,
},
Plus {
reg: u8,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImmSize {
None,
Ib,
Iw,
Id,
Io,
Cd,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Fits {
Any,
Signed8,
Signed32,
Byte,
Word,
Long,
}
impl Fits {
fn holds(self, imm: i64) -> bool {
match self {
Fits::Any => true,
Signed8 => i8::try_from(imm).is_ok(),
Signed32 => i32::try_from(imm).is_ok(),
Fits::Byte => i8::try_from(imm).is_ok() || u8::try_from(imm).is_ok(),
Fits::Word => i16::try_from(imm).is_ok() || u16::try_from(imm).is_ok(),
Fits::Long => i32::try_from(imm).is_ok() || u32::try_from(imm).is_ok(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Encoding {
pub mnemonic: &'static str,
pub args: &'static [Kind],
pub fits: Fits,
pub size: Size,
pub opcode: &'static [u8],
pub fields: Fields,
pub imm: ImmSize,
}
const fn bytes(
mnemonic: &'static str,
args: &'static [Kind],
size: Size,
opcode: &'static [u8],
fields: Fields,
imm: ImmSize,
) -> Encoding {
Encoding { mnemonic, args, fits: Fits::Any, size, opcode, fields, imm }
}
const fn takes(
mnemonic: &'static str,
args: &'static [Kind],
fits: Fits,
size: Size,
opcode: &'static [u8],
fields: Fields,
imm: ImmSize,
) -> Encoding {
Encoding { mnemonic, args, fits, size, opcode, fields, imm }
}
const fn ext(rm: u8, ext: u8) -> Fields {
Fields::Ext { rm, ext }
}
const fn pair(rm: u8, reg: u8) -> Fields {
Fields::Pair { rm, reg }
}
const fn plus(reg: u8) -> Fields {
Fields::Plus { reg }
}
const NO_MODRM: Fields = Fields::None;
const NO_IMM: ImmSize = ImmSize::None;
static NO_ARGS: [Kind; 0] = [];
static R: [Kind; 1] = [Kind::Reg];
static RR: [Kind; 2] = [Kind::Reg, Kind::Reg];
static IR: [Kind; 2] = [Kind::Imm, Kind::Reg];
static IRR: [Kind; 3] = [Kind::Imm, Kind::Reg, Kind::Reg];
static MR: [Kind; 2] = [Kind::Mem, Kind::Reg];
static RM: [Kind; 2] = [Kind::Reg, Kind::Mem];
static D: [Kind; 1] = [Kind::Dest];
static ENCODINGS: &[Encoding] = &[
takes("movb", &IR, Fits::Byte, Byte, &[0xC6], ext(1, 0), ImmSize::Ib),
takes("movw", &IR, Fits::Word, Word, &[0xC7], ext(1, 0), ImmSize::Iw),
takes("movl", &IR, Fits::Long, Long, &[0xC7], ext(1, 0), ImmSize::Id),
takes("movq", &IR, Signed32, Quad, &[0xC7], ext(1, 0), ImmSize::Id),
bytes("movq", &IR, Quad, &[0xB8], plus(1), ImmSize::Io),
bytes("addb", &RR, Byte, &[0x00], pair(1, 0), NO_IMM),
bytes("addw", &RR, Word, &[0x01], pair(1, 0), NO_IMM),
bytes("addl", &RR, Long, &[0x01], pair(1, 0), NO_IMM),
bytes("addq", &RR, Quad, &[0x01], pair(1, 0), NO_IMM),
bytes("subb", &RR, Byte, &[0x28], pair(1, 0), NO_IMM),
bytes("subw", &RR, Word, &[0x29], pair(1, 0), NO_IMM),
bytes("subl", &RR, Long, &[0x29], pair(1, 0), NO_IMM),
bytes("subq", &RR, Quad, &[0x29], pair(1, 0), NO_IMM),
bytes("andb", &RR, Byte, &[0x20], pair(1, 0), NO_IMM),
bytes("andw", &RR, Word, &[0x21], pair(1, 0), NO_IMM),
bytes("andl", &RR, Long, &[0x21], pair(1, 0), NO_IMM),
bytes("andq", &RR, Quad, &[0x21], pair(1, 0), NO_IMM),
bytes("orb", &RR, Byte, &[0x08], pair(1, 0), NO_IMM),
bytes("orw", &RR, Word, &[0x09], pair(1, 0), NO_IMM),
bytes("orl", &RR, Long, &[0x09], pair(1, 0), NO_IMM),
bytes("orq", &RR, Quad, &[0x09], pair(1, 0), NO_IMM),
bytes("xorb", &RR, Byte, &[0x30], pair(1, 0), NO_IMM),
bytes("xorw", &RR, Word, &[0x31], pair(1, 0), NO_IMM),
bytes("xorl", &RR, Long, &[0x31], pair(1, 0), NO_IMM),
bytes("xorq", &RR, Quad, &[0x31], pair(1, 0), NO_IMM),
bytes("imulw", &RR, Word, &[0x0F, 0xAF], pair(0, 1), NO_IMM),
bytes("imull", &RR, Long, &[0x0F, 0xAF], pair(0, 1), NO_IMM),
bytes("imulq", &RR, Quad, &[0x0F, 0xAF], pair(0, 1), NO_IMM),
takes("addb", &IR, Fits::Byte, Byte, &[0x80], ext(1, 0), ImmSize::Ib),
takes("addw", &IR, Signed8, Word, &[0x83], ext(1, 0), ImmSize::Ib),
takes("addw", &IR, Fits::Word, Word, &[0x81], ext(1, 0), ImmSize::Iw),
takes("addl", &IR, Signed8, Long, &[0x83], ext(1, 0), ImmSize::Ib),
takes("addl", &IR, Fits::Long, Long, &[0x81], ext(1, 0), ImmSize::Id),
takes("addq", &IR, Signed8, Quad, &[0x83], ext(1, 0), ImmSize::Ib),
takes("addq", &IR, Signed32, Quad, &[0x81], ext(1, 0), ImmSize::Id),
takes("subb", &IR, Fits::Byte, Byte, &[0x80], ext(1, 5), ImmSize::Ib),
takes("subw", &IR, Signed8, Word, &[0x83], ext(1, 5), ImmSize::Ib),
takes("subw", &IR, Fits::Word, Word, &[0x81], ext(1, 5), ImmSize::Iw),
takes("subl", &IR, Signed8, Long, &[0x83], ext(1, 5), ImmSize::Ib),
takes("subl", &IR, Fits::Long, Long, &[0x81], ext(1, 5), ImmSize::Id),
takes("subq", &IR, Signed8, Quad, &[0x83], ext(1, 5), ImmSize::Ib),
takes("subq", &IR, Signed32, Quad, &[0x81], ext(1, 5), ImmSize::Id),
takes("andb", &IR, Fits::Byte, Byte, &[0x80], ext(1, 4), ImmSize::Ib),
takes("andw", &IR, Signed8, Word, &[0x83], ext(1, 4), ImmSize::Ib),
takes("andw", &IR, Fits::Word, Word, &[0x81], ext(1, 4), ImmSize::Iw),
takes("andl", &IR, Signed8, Long, &[0x83], ext(1, 4), ImmSize::Ib),
takes("andl", &IR, Fits::Long, Long, &[0x81], ext(1, 4), ImmSize::Id),
takes("andq", &IR, Signed8, Quad, &[0x83], ext(1, 4), ImmSize::Ib),
takes("andq", &IR, Signed32, Quad, &[0x81], ext(1, 4), ImmSize::Id),
takes("orb", &IR, Fits::Byte, Byte, &[0x80], ext(1, 1), ImmSize::Ib),
takes("orw", &IR, Signed8, Word, &[0x83], ext(1, 1), ImmSize::Ib),
takes("orw", &IR, Fits::Word, Word, &[0x81], ext(1, 1), ImmSize::Iw),
takes("orl", &IR, Signed8, Long, &[0x83], ext(1, 1), ImmSize::Ib),
takes("orl", &IR, Fits::Long, Long, &[0x81], ext(1, 1), ImmSize::Id),
takes("orq", &IR, Signed8, Quad, &[0x83], ext(1, 1), ImmSize::Ib),
takes("orq", &IR, Signed32, Quad, &[0x81], ext(1, 1), ImmSize::Id),
takes("xorb", &IR, Fits::Byte, Byte, &[0x80], ext(1, 6), ImmSize::Ib),
takes("xorw", &IR, Signed8, Word, &[0x83], ext(1, 6), ImmSize::Ib),
takes("xorw", &IR, Fits::Word, Word, &[0x81], ext(1, 6), ImmSize::Iw),
takes("xorl", &IR, Signed8, Long, &[0x83], ext(1, 6), ImmSize::Ib),
takes("xorl", &IR, Fits::Long, Long, &[0x81], ext(1, 6), ImmSize::Id),
takes("xorq", &IR, Signed8, Quad, &[0x83], ext(1, 6), ImmSize::Ib),
takes("xorq", &IR, Signed32, Quad, &[0x81], ext(1, 6), ImmSize::Id),
takes("imulw", &IRR, Signed8, Word, &[0x6B], pair(1, 2), ImmSize::Ib),
takes("imulw", &IRR, Fits::Word, Word, &[0x69], pair(1, 2), ImmSize::Iw),
takes("imull", &IRR, Signed8, Long, &[0x6B], pair(1, 2), ImmSize::Ib),
takes("imull", &IRR, Fits::Long, Long, &[0x69], pair(1, 2), ImmSize::Id),
takes("imulq", &IRR, Signed8, Quad, &[0x6B], pair(1, 2), ImmSize::Ib),
takes("imulq", &IRR, Signed32, Quad, &[0x69], pair(1, 2), ImmSize::Id),
bytes("negb", &R, Byte, &[0xF6], ext(0, 3), NO_IMM),
bytes("negw", &R, Word, &[0xF7], ext(0, 3), NO_IMM),
bytes("negl", &R, Long, &[0xF7], ext(0, 3), NO_IMM),
bytes("negq", &R, Quad, &[0xF7], ext(0, 3), NO_IMM),
bytes("notb", &R, Byte, &[0xF6], ext(0, 2), NO_IMM),
bytes("notw", &R, Word, &[0xF7], ext(0, 2), NO_IMM),
bytes("notl", &R, Long, &[0xF7], ext(0, 2), NO_IMM),
bytes("notq", &R, Quad, &[0xF7], ext(0, 2), NO_IMM),
bytes("cbtw", &NO_ARGS, Word, &[0x98], NO_MODRM, NO_IMM),
bytes("cwtd", &NO_ARGS, Word, &[0x99], NO_MODRM, NO_IMM),
bytes("cltd", &NO_ARGS, Long, &[0x99], NO_MODRM, NO_IMM),
bytes("cqto", &NO_ARGS, Quad, &[0x99], NO_MODRM, NO_IMM),
bytes("idivb", &R, Byte, &[0xF6], ext(0, 7), NO_IMM),
bytes("idivw", &R, Word, &[0xF7], ext(0, 7), NO_IMM),
bytes("idivl", &R, Long, &[0xF7], ext(0, 7), NO_IMM),
bytes("idivq", &R, Quad, &[0xF7], ext(0, 7), NO_IMM),
bytes("divb", &R, Byte, &[0xF6], ext(0, 6), NO_IMM),
bytes("divw", &R, Word, &[0xF7], ext(0, 6), NO_IMM),
bytes("divl", &R, Long, &[0xF7], ext(0, 6), NO_IMM),
bytes("divq", &R, Quad, &[0xF7], ext(0, 6), NO_IMM),
takes("shlb", &IR, Fits::Byte, Byte, &[0xC0], ext(1, 4), ImmSize::Ib),
takes("shlw", &IR, Fits::Byte, Word, &[0xC1], ext(1, 4), ImmSize::Ib),
takes("shll", &IR, Fits::Byte, Long, &[0xC1], ext(1, 4), ImmSize::Ib),
takes("shlq", &IR, Fits::Byte, Quad, &[0xC1], ext(1, 4), ImmSize::Ib),
takes("shrb", &IR, Fits::Byte, Byte, &[0xC0], ext(1, 5), ImmSize::Ib),
takes("shrw", &IR, Fits::Byte, Word, &[0xC1], ext(1, 5), ImmSize::Ib),
takes("shrl", &IR, Fits::Byte, Long, &[0xC1], ext(1, 5), ImmSize::Ib),
takes("shrq", &IR, Fits::Byte, Quad, &[0xC1], ext(1, 5), ImmSize::Ib),
takes("sarb", &IR, Fits::Byte, Byte, &[0xC0], ext(1, 7), ImmSize::Ib),
takes("sarw", &IR, Fits::Byte, Word, &[0xC1], ext(1, 7), ImmSize::Ib),
takes("sarl", &IR, Fits::Byte, Long, &[0xC1], ext(1, 7), ImmSize::Ib),
takes("sarq", &IR, Fits::Byte, Quad, &[0xC1], ext(1, 7), ImmSize::Ib),
bytes("shlb", &RR, Byte, &[0xD2], ext(1, 4), NO_IMM),
bytes("shlw", &RR, Word, &[0xD3], ext(1, 4), NO_IMM),
bytes("shll", &RR, Long, &[0xD3], ext(1, 4), NO_IMM),
bytes("shlq", &RR, Quad, &[0xD3], ext(1, 4), NO_IMM),
bytes("shrb", &RR, Byte, &[0xD2], ext(1, 5), NO_IMM),
bytes("shrw", &RR, Word, &[0xD3], ext(1, 5), NO_IMM),
bytes("shrl", &RR, Long, &[0xD3], ext(1, 5), NO_IMM),
bytes("shrq", &RR, Quad, &[0xD3], ext(1, 5), NO_IMM),
bytes("sarb", &RR, Byte, &[0xD2], ext(1, 7), NO_IMM),
bytes("sarw", &RR, Word, &[0xD3], ext(1, 7), NO_IMM),
bytes("sarl", &RR, Long, &[0xD3], ext(1, 7), NO_IMM),
bytes("sarq", &RR, Quad, &[0xD3], ext(1, 7), NO_IMM),
bytes("cmpb", &RR, Byte, &[0x38], pair(1, 0), NO_IMM),
bytes("cmpw", &RR, Word, &[0x39], pair(1, 0), NO_IMM),
bytes("cmpl", &RR, Long, &[0x39], pair(1, 0), NO_IMM),
bytes("cmpq", &RR, Quad, &[0x39], pair(1, 0), NO_IMM),
bytes("sete", &R, Byte, &[0x0F, 0x94], ext(0, 0), NO_IMM),
bytes("setne", &R, Byte, &[0x0F, 0x95], ext(0, 0), NO_IMM),
bytes("setl", &R, Byte, &[0x0F, 0x9C], ext(0, 0), NO_IMM),
bytes("setle", &R, Byte, &[0x0F, 0x9E], ext(0, 0), NO_IMM),
bytes("setg", &R, Byte, &[0x0F, 0x9F], ext(0, 0), NO_IMM),
bytes("setge", &R, Byte, &[0x0F, 0x9D], ext(0, 0), NO_IMM),
bytes("setb", &R, Byte, &[0x0F, 0x92], ext(0, 0), NO_IMM),
bytes("setbe", &R, Byte, &[0x0F, 0x96], ext(0, 0), NO_IMM),
bytes("seta", &R, Byte, &[0x0F, 0x97], ext(0, 0), NO_IMM),
bytes("setae", &R, Byte, &[0x0F, 0x93], ext(0, 0), NO_IMM),
bytes("movzbw", &RR, Word, &[0x0F, 0xB6], pair(0, 1), NO_IMM),
bytes("movzbl", &RR, Long, &[0x0F, 0xB6], pair(0, 1), NO_IMM),
bytes("movzbq", &RR, Quad, &[0x0F, 0xB6], pair(0, 1), NO_IMM),
bytes("movzwl", &RR, Long, &[0x0F, 0xB7], pair(0, 1), NO_IMM),
bytes("movzwq", &RR, Quad, &[0x0F, 0xB7], pair(0, 1), NO_IMM),
bytes("movsbw", &RR, Word, &[0x0F, 0xBE], pair(0, 1), NO_IMM),
bytes("movsbl", &RR, Long, &[0x0F, 0xBE], pair(0, 1), NO_IMM),
bytes("movsbq", &RR, Quad, &[0x0F, 0xBE], pair(0, 1), NO_IMM),
bytes("movswl", &RR, Long, &[0x0F, 0xBF], pair(0, 1), NO_IMM),
bytes("movswq", &RR, Quad, &[0x0F, 0xBF], pair(0, 1), NO_IMM),
bytes("movslq", &RR, Quad, &[0x63], pair(0, 1), NO_IMM),
bytes("movb", &RR, Byte, &[0x88], pair(1, 0), NO_IMM),
bytes("movw", &RR, Word, &[0x89], pair(1, 0), NO_IMM),
bytes("movl", &RR, Long, &[0x89], pair(1, 0), NO_IMM),
bytes("movq", &RR, Quad, &[0x89], pair(1, 0), NO_IMM),
bytes("leaq", &MR, Quad, &[0x8D], pair(0, 1), NO_IMM),
bytes("movb", &MR, Byte, &[0x8A], pair(0, 1), NO_IMM),
bytes("movw", &MR, Word, &[0x8B], pair(0, 1), NO_IMM),
bytes("movl", &MR, Long, &[0x8B], pair(0, 1), NO_IMM),
bytes("movq", &MR, Quad, &[0x8B], pair(0, 1), NO_IMM),
bytes("movb", &RM, Byte, &[0x88], pair(1, 0), NO_IMM),
bytes("movw", &RM, Word, &[0x89], pair(1, 0), NO_IMM),
bytes("movl", &RM, Long, &[0x89], pair(1, 0), NO_IMM),
bytes("movq", &RM, Quad, &[0x89], pair(1, 0), NO_IMM),
bytes("call", &D, Long, &[0xE8], NO_MODRM, ImmSize::Cd),
bytes("testb", &RR, Byte, &[0x84], pair(1, 0), NO_IMM),
bytes("je", &D, Long, &[0x0F, 0x84], NO_MODRM, ImmSize::Cd),
bytes("jne", &D, Long, &[0x0F, 0x85], NO_MODRM, ImmSize::Cd),
bytes("jmp", &D, Long, &[0xE9], NO_MODRM, ImmSize::Cd),
bytes("pushq", &R, Long, &[0x50], plus(0), NO_IMM),
bytes("popq", &R, Long, &[0x58], plus(0), NO_IMM),
bytes("ret", &NO_ARGS, Long, &[0xC3], NO_MODRM, NO_IMM),
bytes("movaps", &RR, Long, &[0x0F, 0x28], pair(0, 1), NO_IMM),
bytes("movaps", &MR, Long, &[0x0F, 0x28], pair(0, 1), NO_IMM),
bytes("movaps", &RM, Long, &[0x0F, 0x29], pair(1, 0), NO_IMM),
];
#[must_use]
pub fn encoding(mnemonic: &str, args: &[Kind], imm: i64) -> Option<&'static Encoding> {
rows(mnemonic, args).find(|row| row.fits.holds(imm))
}
fn rows<'a>(mnemonic: &'a str, args: &'a [Kind]) -> impl Iterator<Item = &'static Encoding> + 'a {
ENCODINGS.iter().filter(move |row| row.mnemonic == mnemonic && row.args == args)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Addr {
pub base: Option<PhysReg>,
pub index: Option<PhysReg>,
pub scale: u8,
pub disp: i32,
pub rip: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Value {
Reg(PhysReg, Width),
High(PhysReg),
Mem(Addr),
Imm(i64),
Dest,
}
impl Value {
#[must_use]
pub fn kind(self) -> Kind {
match self {
Value::Reg(_, _) | Value::High(_) => Kind::Reg,
Value::Mem(_) => Kind::Mem,
Value::Imm(_) => Kind::Imm,
Value::Dest => Kind::Dest,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Holes {
pub dest: Option<usize>,
pub rip: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
Unwritten {
mnemonic: String,
args: Vec<Kind>,
},
Immediate {
mnemonic: String,
imm: i64,
},
Crowded {
mnemonic: String,
},
Scale {
scale: u8,
},
Index,
Argument {
mnemonic: String,
at: u8,
},
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Unwritten { mnemonic, args } => {
write!(f, "no encoding for {mnemonic} with {} arguments {args:?}", args.len())
}
Error::Immediate { mnemonic, imm } => {
write!(f, "no form of {mnemonic} can carry the immediate {imm}")
}
Error::Crowded { mnemonic } => {
write!(f, "{mnemonic} names ah and a register that needs a rex byte")
}
Error::Scale { scale } => write!(f, "{scale} is not a scale this machine has"),
Error::Index => write!(f, "the stack pointer cannot be an index"),
Error::Argument { mnemonic, at } => {
write!(f, "argument {at} of {mnemonic} is not what its encoding expects")
}
}
}
}
impl std::error::Error for Error {}
const REX_W: u8 = 0b1000;
const REX_R: u8 = 0b0100;
const REX_X: u8 = 0b0010;
const REX_B: u8 = 0b0001;
pub fn encode(mnemonic: &str, values: &[Value], out: &mut Vec<u8>) -> Result<Holes, Error> {
let args: Vec<Kind> = values.iter().map(|value| value.kind()).collect();
let imm = values
.iter()
.find_map(|value| match value {
Value::Imm(number) => Some(*number),
_ => None,
})
.unwrap_or(0);
let Some(row) = encoding(mnemonic, &args, imm) else {
return Err(if rows(mnemonic, &args).next().is_some() {
Error::Immediate { mnemonic: mnemonic.to_owned(), imm }
} else {
Error::Unwritten { mnemonic: mnemonic.to_owned(), args }
});
};
Writer { row, values, rex: 0, forced: false, banned: false }.write(out, imm)
}
struct Writer<'a> {
row: &'a Encoding,
values: &'a [Value],
rex: u8,
forced: bool,
banned: bool,
}
impl Writer<'_> {
fn write(mut self, out: &mut Vec<u8>, imm: i64) -> Result<Holes, Error> {
let mut tail = Vec::new();
let mut holes = Holes::default();
let mut plus = 0;
match self.row.fields {
Fields::None => {}
Fields::Ext { rm, ext } => self.address(rm, ext, &mut tail, &mut holes)?,
Fields::Pair { rm, reg } => {
let reg = self.number(reg, REX_R)?;
self.address(rm, reg, &mut tail, &mut holes)?;
}
Fields::Plus { reg } => plus = self.number(reg, REX_B)?,
}
if self.banned && (self.forced || self.rex != 0) {
return Err(Error::Crowded { mnemonic: self.row.mnemonic.to_owned() });
}
if self.row.size == Word {
out.push(0x66);
}
let rex = if self.row.size == Quad { self.rex | REX_W } else { self.rex };
if rex != 0 || (self.forced && !self.banned) {
out.push(0x40 | rex);
}
let (last, front) = self.row.opcode.split_last().expect("an opcode is at least one byte");
out.extend_from_slice(front);
out.push(last + plus);
let at = out.len();
for hole in [&mut holes.dest, &mut holes.rip].into_iter().flatten() {
*hole += at;
}
out.extend_from_slice(&tail);
match self.row.imm {
ImmSize::None => {}
ImmSize::Ib => out.push(imm as u8),
ImmSize::Iw => out.extend_from_slice(&(imm as u16).to_le_bytes()),
ImmSize::Id => out.extend_from_slice(&(imm as u32).to_le_bytes()),
ImmSize::Io => out.extend_from_slice(&imm.to_le_bytes()),
ImmSize::Cd => {
holes.dest = Some(out.len());
out.extend_from_slice(&0i32.to_le_bytes());
}
}
Ok(holes)
}
fn number(&mut self, at: u8, bit: u8) -> Result<u8, Error> {
match self.values.get(usize::from(at)) {
Some(&Value::Reg(reg, width)) => {
let number = reg.number();
if number >= 8 {
self.rex |= bit;
}
if width == Width::Byte && (4..8).contains(&number) {
self.forced = true;
}
Ok(number & 7)
}
Some(&Value::High(reg)) if reg.number() < 4 => {
self.banned = true;
Ok(reg.number() + 4)
}
_ => Err(Error::Argument { mnemonic: self.row.mnemonic.to_owned(), at }),
}
}
fn address(
&mut self,
at: u8,
reg: u8,
out: &mut Vec<u8>,
holes: &mut Holes,
) -> Result<(), Error> {
match self.values.get(usize::from(at)) {
Some(Value::Mem(addr)) => self.mem(*addr, reg, out, holes),
Some(_) => {
let rm = self.number(at, REX_B)?;
out.push(0b1100_0000 | (reg << 3) | rm);
Ok(())
}
None => Err(Error::Argument { mnemonic: self.row.mnemonic.to_owned(), at }),
}
}
fn mem(
&mut self,
addr: Addr,
reg: u8,
out: &mut Vec<u8>,
holes: &mut Holes,
) -> Result<(), Error> {
if addr.rip {
out.push((reg << 3) | 0b101);
holes.rip = Some(out.len());
out.extend_from_slice(&addr.disp.to_le_bytes());
return Ok(());
}
let index = match addr.index {
Some(index) if index.number() == 4 => return Err(Error::Index),
Some(index) => {
if index.number() >= 8 {
self.rex |= REX_X;
}
Some(index.number() & 7)
}
None => None,
};
let scale = match addr.scale {
_ if index.is_none() => 0,
1 => 0,
2 => 1,
4 => 2,
8 => 3,
scale => return Err(Error::Scale { scale }),
};
let base = addr.base.map(|base| {
if base.number() >= 8 {
self.rex |= REX_B;
}
base.number() & 7
});
let second = index.is_some() || base == Some(4) || base.is_none();
let mode = match base {
None => 0,
Some(base) => {
if addr.disp == 0 && base != 5 {
0
} else if i8::try_from(addr.disp).is_ok() {
1
} else {
2
}
}
};
out.push((mode << 6) | (reg << 3) | if second { 0b100 } else { base.unwrap_or(0) });
if second {
out.push((scale << 6) | (index.unwrap_or(4) << 3) | base.unwrap_or(5));
}
match mode {
0 if base.is_none() => out.extend_from_slice(&addr.disp.to_le_bytes()),
0 => {}
1 => out.push(addr.disp as u8),
_ => out.extend_from_slice(&addr.disp.to_le_bytes()),
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::x86_64::text::written;
use crate::x86_64::{INSTS, R8, R12, R13, RAX, RBP, RCX, RDX, RSI, RSP};
fn hex(mnemonic: &str, values: &[Value]) -> String {
let mut out = Vec::new();
encode(mnemonic, values, &mut out).expect("an instruction this target encodes");
out.iter().map(|byte| format!("{byte:02x}")).collect::<Vec<_>>().join(" ")
}
fn quad(reg: PhysReg) -> Value {
Value::Reg(reg, Width::Quad)
}
fn long(reg: PhysReg) -> Value {
Value::Reg(reg, Width::Long)
}
fn byte(reg: PhysReg) -> Value {
Value::Reg(reg, Width::Byte)
}
#[test]
fn every_instruction_the_listing_writes_is_one_this_encodes() {
for &(opcode, _) in INSTS {
let insts = written(opcode).expect("every described opcode is a written opcode");
for inst in insts {
let args: Vec<Kind> = inst.args.iter().map(|&arg| Kind::of(arg)).collect();
assert!(
encoding(inst.mnemonic, &args, 0).is_some(),
"{opcode} writes {} with {args:?} and nothing encodes it",
inst.mnemonic
);
}
}
}
const PROBES: [i64; 11] =
[0, 1, -1, 127, 128, -128, -129, 0xffff, 0x1_0000, 0x7fff_ffff, 0x1_0000_0000];
#[test]
fn the_rows_of_one_instruction_go_from_the_smallest_immediate_to_the_largest() {
for (at, row) in ENCODINGS.iter().enumerate() {
for other in &ENCODINGS[at + 1..] {
if other.mnemonic != row.mnemonic || other.args != row.args {
continue;
}
for imm in PROBES {
assert!(
!row.fits.holds(imm) || other.fits.holds(imm),
"{} takes {imm} in front of a row that does not",
row.mnemonic
);
}
assert!(
PROBES.iter().any(|&imm| other.fits.holds(imm) && !row.fits.holds(imm)),
"{} has a row behind another that holds no more than it",
row.mnemonic
);
}
}
}
#[test]
fn an_immediate_no_form_of_an_instruction_can_hold_is_refused_rather_than_cut_down() {
let mut out = Vec::new();
let big = 0x1_2345_6789;
let error = encode("addq", &[Value::Imm(big), quad(RAX)], &mut out)
.expect_err("more than four bytes of immediate");
assert_eq!(error, Error::Immediate { mnemonic: "addq".to_owned(), imm: big });
assert_eq!(out, Vec::<u8>::new());
assert!(encode("movq", &[Value::Imm(big), quad(RAX)], &mut out).is_ok());
assert_eq!(hex("movl", &[Value::Imm(0xffff_ffff), long(RAX)]), "c7 c0 ff ff ff ff");
assert_eq!(hex("addb", &[Value::Imm(200), byte(RAX)]), "80 c0 c8");
assert_eq!(hex("shlq", &[Value::Imm(63), quad(RAX)]), "48 c1 e0 3f");
}
#[test]
fn an_instruction_with_two_registers_is_the_opcode_and_one_byte_that_names_both() {
assert_eq!(hex("addl", &[long(RCX), long(RAX)]), "01 c8");
assert_eq!(hex("addl", &[long(RAX), long(RCX)]), "01 c1");
assert_eq!(hex("addq", &[quad(RCX), quad(RAX)]), "48 01 c8");
let word = [Value::Reg(RCX, Width::Word), Value::Reg(RAX, Width::Word)];
assert_eq!(hex("addw", &word), "66 01 c8");
assert_eq!(hex("imull", &[long(RCX), long(RAX)]), "0f af c1");
}
#[test]
fn a_register_the_second_half_of_the_machine_added_is_named_in_the_byte_in_front() {
assert_eq!(hex("addl", &[long(R8), long(RAX)]), "44 01 c0");
assert_eq!(hex("addl", &[long(RAX), long(R8)]), "41 01 c0");
assert_eq!(hex("addq", &[quad(R8), quad(R8)]), "4d 01 c0");
assert_eq!(hex("pushq", &[quad(R12)]), "41 54");
assert_eq!(hex("popq", &[quad(RAX)]), "58");
}
#[test]
fn a_byte_register_the_machine_could_not_reach_before_forces_a_byte_that_says_nothing_else() {
assert_eq!(hex("movb", &[byte(RSI), byte(RAX)]), "40 88 f0");
assert_eq!(hex("sete", &[byte(RSI)]), "40 0f 94 c6");
assert_eq!(hex("sete", &[byte(RAX)]), "0f 94 c0");
assert_eq!(hex("movb", &[Value::High(RAX), byte(RDX)]), "88 e2");
let mut out = Vec::new();
let error = encode("movb", &[Value::High(RAX), byte(RSI)], &mut out)
.expect_err("ah and sil in one instruction");
assert_eq!(error, Error::Crowded { mnemonic: "movb".to_owned() });
}
#[test]
fn an_immediate_is_written_in_as_few_bytes_as_it_fits_in() {
assert_eq!(hex("addl", &[Value::Imm(1), long(RCX)]), "83 c1 01");
assert_eq!(hex("addl", &[Value::Imm(-1), long(RCX)]), "83 c1 ff");
assert_eq!(hex("addl", &[Value::Imm(1000), long(RCX)]), "81 c1 e8 03 00 00");
assert_eq!(hex("addq", &[Value::Imm(8), quad(RSP)]), "48 83 c4 08");
assert_eq!(hex("movq", &[Value::Imm(1), quad(RAX)]), "48 c7 c0 01 00 00 00");
assert_eq!(
hex("movq", &[Value::Imm(0x1_2345_6789), quad(RAX)]),
"48 b8 89 67 45 23 01 00 00 00"
);
assert_eq!(hex("movl", &[Value::Imm(1), long(RAX)]), "c7 c0 01 00 00 00");
}
#[test]
fn an_address_is_the_registers_it_names_and_whatever_is_added_to_them() {
let base = Addr { base: Some(RCX), ..Addr::default() };
assert_eq!(hex("movq", &[Value::Mem(base), quad(RAX)]), "48 8b 01");
let near = Addr { base: Some(RCX), disp: -16, ..Addr::default() };
assert_eq!(hex("movq", &[Value::Mem(near), quad(RAX)]), "48 8b 41 f0");
let far = Addr { base: Some(RCX), disp: 1000, ..Addr::default() };
assert_eq!(hex("movq", &[Value::Mem(far), quad(RAX)]), "48 8b 81 e8 03 00 00");
let indexed = Addr { base: Some(RCX), index: Some(RDX), scale: 4, disp: -16, rip: false };
assert_eq!(hex("leaq", &[Value::Mem(indexed), quad(RAX)]), "48 8d 44 91 f0");
assert_eq!(hex("movl", &[long(RAX), Value::Mem(near)]), "89 41 f0");
}
#[test]
fn the_two_registers_an_address_cannot_be_written_with_plainly_are_written_around() {
let stack = Addr { base: Some(RSP), disp: 8, ..Addr::default() };
assert_eq!(hex("movq", &[Value::Mem(stack), quad(RAX)]), "48 8b 44 24 08");
let frame = Addr { base: Some(RBP), ..Addr::default() };
assert_eq!(hex("movq", &[Value::Mem(frame), quad(RAX)]), "48 8b 45 00");
let twelve = Addr { base: Some(R12), disp: 8, ..Addr::default() };
assert_eq!(hex("movq", &[Value::Mem(twelve), quad(RAX)]), "49 8b 44 24 08");
let thirteen = Addr { base: Some(R13), ..Addr::default() };
assert_eq!(hex("movq", &[Value::Mem(thirteen), quad(RAX)]), "49 8b 45 00");
let mut out = Vec::new();
let bad = Addr { base: Some(RCX), index: Some(RSP), scale: 1, disp: 0, rip: false };
let error = encode("leaq", &[Value::Mem(bad), quad(RAX)], &mut out)
.expect_err("the stack pointer as an index");
assert_eq!(error, Error::Index);
}
#[test]
fn an_address_counted_from_the_end_of_the_instruction_leaves_its_displacement_open() {
let global = Addr { rip: true, ..Addr::default() };
let mut out = vec![0xcc];
let holes = encode("movq", &[Value::Mem(global), quad(RAX)], &mut out).expect("a global");
assert_eq!(out, [0xcc, 0x48, 0x8b, 0x05, 0, 0, 0, 0]);
assert_eq!(holes.rip, Some(4));
assert_eq!(holes.dest, None);
}
#[test]
fn a_jump_leaves_the_distance_to_where_it_goes_open() {
let mut out = Vec::new();
let holes = encode("jmp", &[Value::Dest], &mut out).expect("a jump");
assert_eq!(out, [0xe9, 0, 0, 0, 0]);
assert_eq!(holes.dest, Some(1));
out.clear();
let holes = encode("je", &[Value::Dest], &mut out).expect("a conditional jump");
assert_eq!(out, [0x0f, 0x84, 0, 0, 0, 0]);
assert_eq!(holes.dest, Some(2));
}
#[test]
fn an_instruction_with_no_arguments_is_the_opcode_and_whatever_says_how_wide_it_is() {
assert_eq!(hex("ret", &[]), "c3");
assert_eq!(hex("cltd", &[]), "99");
assert_eq!(hex("cqto", &[]), "48 99");
assert_eq!(hex("cwtd", &[]), "66 99");
assert_eq!(hex("cbtw", &[]), "66 98");
}
#[test]
fn a_shift_by_a_count_does_not_encode_the_count_because_the_machine_knows_where_it_is() {
assert_eq!(hex("shlq", &[byte(RCX), quad(RAX)]), "48 d3 e0");
assert_eq!(hex("sarl", &[byte(RCX), long(RCX)]), "d3 f9");
assert_eq!(hex("shll", &[Value::Imm(3), long(RAX)]), "c1 e0 03");
}
#[test]
fn a_division_is_the_widening_and_then_the_instruction_that_names_only_its_divisor() {
assert_eq!(hex("idivl", &[long(RCX)]), "f7 f9");
assert_eq!(hex("idivq", &[quad(RSI)]), "48 f7 fe");
assert_eq!(hex("divl", &[long(RCX)]), "f7 f1");
assert_eq!(hex("negl", &[long(RAX)]), "f7 d8");
assert_eq!(hex("notq", &[quad(RAX)]), "48 f7 d0");
}
#[test]
fn a_conversion_puts_its_destination_where_the_arithmetic_puts_its_source() {
assert_eq!(hex("movzbl", &[byte(RAX), long(RCX)]), "0f b6 c8");
assert_eq!(hex("movsbq", &[byte(RAX), quad(RCX)]), "48 0f be c8");
assert_eq!(hex("movslq", &[long(RSI), quad(RAX)]), "48 63 c6");
assert_eq!(hex("movzwl", &[Value::Reg(RSI, Width::Word), long(RAX)]), "0f b7 c6");
}
#[test]
fn a_mnemonic_with_arguments_it_does_not_take_is_refused_rather_than_encoded() {
let mut out = Vec::new();
let error = encode("ret", &[quad(RAX)], &mut out).expect_err("a return of a register");
assert_eq!(error, Error::Unwritten { mnemonic: "ret".to_owned(), args: vec![Kind::Reg] });
assert_eq!(out, Vec::<u8>::new(), "nothing is written for an instruction that is refused");
let error = encode("frobnicate", &[], &mut out).expect_err("no such instruction");
assert!(matches!(error, Error::Unwritten { .. }), "{error}");
assert_eq!(encoding("addl", &[Kind::Reg], 0), None);
}
}