use rucc_target::x86_64::{
Addr, Encoding, ImmSize, Length, Opmask, RAX, Value, Width, encode_masked, encoding, gpr_named,
xmm,
};
use rucc_target::{PhysReg, Segment};
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Hole {
pub at: usize,
pub width: u8,
pub name: String,
pub addend: i64,
pub sort: Sort,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Sort {
Branch,
Near,
Value,
Table,
Thread,
}
fn reached(named: &str) -> Result<(String, Sort), String> {
let Some((name, how)) = named.split_once('@') else {
return Ok((named.to_owned(), Sort::Near));
};
match how {
"GOTPCREL" => Ok((name.to_owned(), Sort::Table)),
"GOTTPOFF" => Ok((name.to_owned(), Sort::Thread)),
_ => Err(format!("'@{how}' is not a way of reaching something this compiler reads")),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Written {
pub bytes: Vec<u8>,
pub holes: Vec<Hole>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct Named {
name: String,
addend: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Operand {
Reg(PhysReg, Width),
High(PhysReg),
Xmm(PhysReg),
Vector(u8, Length),
Mask(u8),
Stack(u8),
Mem(Addr, Option<Named>),
Imm(i64),
Expr(String),
Dest(Named),
}
const STANDING: i64 = 0x1000_0000;
pub(crate) fn one(word: &str, args: &[String]) -> Result<Written, String> {
let mut mask = Opmask::default();
let mut operands = Vec::with_capacity(args.len());
for arg in args {
let (text, said) = masked(arg.trim())?;
if let Some(said) = said {
if mask != Opmask::default() {
return Err("an instruction has one mask and this one names two".to_owned());
}
mask = said;
}
operands.push(operand(text)?);
}
let predicated = predicated(word);
let word = match &predicated {
Some((name, which)) => {
operands.insert(0, Operand::Imm(*which));
name.as_str()
}
None => word,
};
if matches!(word, "sha256rnds2" | "pblendvb" | "blendvps" | "blendvpd") && operands.len() == 3 {
if operands[0] != Operand::Xmm(xmm(0)) {
return Err(format!("'{word}' reads its third operand from xmm0 and no other"));
}
operands.remove(0);
}
if !BRANCHES.iter().any(|branch| word.starts_with(branch)) {
for operand in &mut operands {
outright(operand);
}
}
let mut values: Vec<Value> = operands.iter().map(|op| value(op, STANDING)).collect();
let (mnemonic, row) = match spelled(word, &operands, &values) {
Ok(found) => found,
Err(_) if operands.iter().any(|op| matches!(op, Operand::Expr(_))) => {
values = operands.iter().map(|op| value(op, 0)).collect();
spelled(word, &operands, &values)?
}
Err(why) => return Err(why),
};
let named: Vec<u8> = operands
.iter()
.filter_map(|op| match op {
Operand::Stack(depth) => Some(*depth),
_ => None,
})
.collect();
let row_depths = depths(&mnemonic);
let which = row_depths.iter().position(|&depth| depth != 0).unwrap_or(0);
let fits = named.len() == row_depths.len()
&& named.iter().zip(row_depths).enumerate().all(|(at, (n, r))| at == which || n == r);
if !named.is_empty() && !fits {
return Err(format!(
"'{word}' at those depths of the x87 stack is not one this compiler has"
));
}
if mnemonic == "fnstsw"
&& operands.iter().any(|op| matches!(op, Operand::Reg(reg, _) if *reg != RAX))
{
return Err(format!("'{word}' only writes the status word into ax"));
}
let mut bytes = Vec::with_capacity(16);
let holes =
encode_masked(&mnemonic, &values, mask, &mut bytes).map_err(|why| why.to_string())?;
if !named.is_empty() {
if let Some(last) = bytes.last_mut() {
*last = last.wrapping_add(named[which]).wrapping_sub(row_depths[which]);
}
}
if holes.dest.is_none() {
shorter(&mut bytes, &operands);
}
let mut wanted = Vec::new();
if let Some(at) = holes.dest {
let Some(Operand::Dest(Named { name, addend })) =
operands.iter().find(|op| matches!(op, Operand::Dest(_)))
else {
return Err(format!("'{word}' left room for somewhere to go and was given nowhere"));
};
if name == "." {
return Ok(Written { bytes: counted(bytes, at, *addend)?, holes: Vec::new() });
}
let width = match row.imm {
ImmSize::Cb => 1,
_ => 4,
};
let name = match name.split_once('@') {
None => name.clone(),
Some((name, "PLT")) => name.to_owned(),
Some((_, how)) => {
return Err(format!("'@{how}' is not a way of reaching somewhere to go"));
}
};
wanted.push(Hole { at, width, name, addend: *addend, sort: Sort::Branch });
}
if let Some(at) = holes.rip {
let named = operands.iter().find_map(|op| match op {
Operand::Mem(_, Some(named)) => Some(named.clone()),
_ => None,
});
if let Some(named) = named {
let (name, sort) = reached(&named.name)?;
wanted.push(Hole { at, width: 4, name, addend: named.addend, sort });
}
}
if let Some(text) = operands.iter().find_map(|op| match op {
Operand::Expr(text) => Some(text.clone()),
_ => None,
}) {
let width = match row.imm {
ImmSize::Ib => 1,
ImmSize::Iw => 2,
ImmSize::Id => 4,
_ => return Err(format!("'{word}' carries '{text}' somewhere this cannot write one")),
};
let at = bytes.len() - width;
wanted.push(Hole { at, width: width as u8, name: text, addend: 0, sort: Sort::Value });
}
Ok(Written { bytes, holes: wanted })
}
pub(crate) fn short(long: &Written) -> Option<Written> {
let [hole] = long.holes.as_slice() else { return None };
if hole.sort != Sort::Branch || hole.width != 4 || hole.at + 4 != long.bytes.len() {
return None;
}
let code = match long.bytes.as_slice() {
[0xE9, ..] if hole.at == 1 => 0xEB,
[0x0F, code @ 0x80..=0x8F, ..] if hole.at == 2 => code - 0x10,
_ => return None,
};
Some(Written { bytes: vec![code, 0], holes: vec![Hole { at: 1, width: 1, ..hole.clone() }] })
}
fn shorter(bytes: &mut Vec<u8>, operands: &[Operand]) {
let mut at = 0;
let mut far = false;
while at < bytes.len() && (bytes[at] == 0x66 || (bytes[at] & 0xF0 == 0x40)) {
far |= bytes[at] & 0xF0 == 0x40 && bytes[at] & 1 != 0;
at += 1;
}
let (Some(&code), Some(&modrm)) = (bytes.get(at), bytes.get(at + 1)) else { return };
let digit = (modrm >> 3) & 7;
if matches!(code, 0xC0 | 0xC1)
&& operands.first() == Some(&Operand::Imm(1))
&& bytes.last() == Some(&1)
{
bytes[at] = code + 0x10;
bytes.pop();
return;
}
if far || modrm != 0xC0 | (digit << 3) {
return;
}
let short = match (code, digit) {
(0x80, _) => (digit << 3) | 0x04,
(0x81, _) => (digit << 3) | 0x05,
(0xF6, 0) => 0xA8,
(0xF7, 0) => 0xA9,
_ => return,
};
bytes[at] = short;
bytes.remove(at + 1);
}
fn counted(mut long: Vec<u8>, at: usize, from_start: i64) -> Result<Vec<u8>, String> {
let short = match long.as_slice() {
[0xE9, ..] if at == 1 => Some(0xEB),
[0x0F, code @ 0x80..=0x8F, ..] if at == 2 => Some(code - 0x10),
_ => None,
};
if let Some(code) = short {
if let Ok(distance) = i8::try_from(from_start - 2) {
return Ok(vec![code, distance as u8]);
}
}
let distance = i32::try_from(from_start - long.len() as i64)
.map_err(|_| format!("'.+{from_start}' is further than a branch reaches"))?;
long[at..at + 4].copy_from_slice(&distance.to_le_bytes());
Ok(long)
}
const PREDICATES: [&str; 8] = ["eq", "lt", "le", "unord", "neq", "nlt", "nle", "ord"];
fn predicated(word: &str) -> Option<(String, i64)> {
let rest = word.strip_prefix("cmp")?;
let (predicate, format) = rest.split_at(rest.len().checked_sub(2)?);
if !matches!(format, "ss" | "sd" | "ps" | "pd") {
return None;
}
let which = PREDICATES.iter().position(|&known| known == predicate)?;
Some((format!("cmp{format}"), which as i64))
}
const BRANCHES: [&str; 4] = ["j", "call", "loop", "xbegin"];
fn outright(operand: &mut Operand) {
let Operand::Dest(Named { name, addend: 0 }) = operand else { return };
let Some(disp) = number(name).ok().and_then(|value| i32::try_from(value).ok()) else {
return;
};
*operand = Operand::Mem(Addr { disp, scale: 1, ..Addr::default() }, None);
}
fn spelled(
word: &str,
operands: &[Operand],
values: &[Value],
) -> Result<(String, &'static Encoding), String> {
if matches!(word, "crc32" | "crc32b")
&& matches!(operands.last(), Some(Operand::Reg(_, Width::Quad)))
&& (word == "crc32b"
|| matches!(operands.first(), Some(Operand::Reg(_, Width::Byte) | Operand::High(_))))
{
return Err(format!(
"'{word}' of a byte into a sixty four bit register is not written yet, and the thirty \
two bit register of the same number gives the same answer"
));
}
let kinds: Vec<_> = 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 names = [Some(word.to_owned()), aliased(word)];
for name in names.iter().flatten() {
if let Some(row) = encoding(name, &kinds, imm) {
return Ok((name.clone(), row));
}
}
if let Some(width) = stated(word, operands)? {
let letter = match width {
Width::Byte => 'b',
Width::Word => 'w',
Width::Long => 'l',
Width::Quad => 'q',
};
for name in names.iter().flatten() {
let spelled = format!("{name}{letter}");
if let Some(row) = encoding(&spelled, &kinds, imm) {
return Ok((spelled, row));
}
}
}
Err(format!(
"'{word}' with {} of those operands is not an instruction this compiler writes yet",
kinds.len()
))
}
const CONDITIONS: &[(&str, &str)] = &[
("z", "e"),
("nz", "ne"),
("c", "b"),
("nc", "ae"),
("nae", "b"),
("nb", "ae"),
("na", "be"),
("nbe", "a"),
("ng", "le"),
("nge", "l"),
("nl", "ge"),
("nle", "g"),
("pe", "p"),
("po", "np"),
];
fn aliased(word: &str) -> Option<String> {
if let Some(rest) = word.strip_prefix("sal") {
if rest.is_empty() || matches!(rest, "b" | "w" | "l" | "q") {
return Some(format!("shl{rest}"));
}
}
if let Some(known) = ["push", "pop", "pushf", "popf"].iter().find(|&&known| known == word) {
return Some(format!("{known}q"));
}
let (prefix, rest) = ["cmov", "set", "j"]
.iter()
.find_map(|prefix| word.strip_prefix(prefix).map(|rest| (*prefix, rest)))?;
let mut tails = vec![(rest, "")];
if rest.len() > 1 && matches!(&rest[rest.len() - 1..], "b" | "w" | "l" | "q") {
tails.push((&rest[..rest.len() - 1], &rest[rest.len() - 1..]));
}
tails.into_iter().find_map(|(condition, tail)| {
let (_, known) = CONDITIONS.iter().find(|(written, _)| *written == condition)?;
Some(format!("{prefix}{known}{tail}"))
})
}
const COUNTED: &[&str] = &["shl", "shr", "sar", "sal", "rol", "ror", "rcl", "rcr", "shld", "shrd"];
fn stated(word: &str, operands: &[Operand]) -> Result<Option<Width>, String> {
if word == "crc32" {
return Ok(match operands.first() {
Some(Operand::Reg(_, width)) => Some(*width),
Some(Operand::High(_)) => Some(Width::Byte),
_ => None,
});
}
let mut width = None;
let counted = COUNTED.contains(&word) && operands.len() > 1;
for operand in operands.iter().skip(usize::from(counted)) {
let said = match operand {
Operand::Reg(_, width) => *width,
Operand::High(_) => Width::Byte,
_ => continue,
};
match width {
None => width = Some(said),
Some(before) if before == said => {}
Some(before) => {
return Err(format!(
"the operands are {} bits and {} bits, so the instruction does not say how \
wide it is",
before.bits(),
said.bits()
));
}
}
}
Ok(width)
}
fn masked(text: &str) -> Result<(&str, Option<Opmask>), String> {
let Some(cut) = text.find('{') else { return Ok((text, None)) };
let mut mask = Opmask::default();
let mut rest = &text[cut..];
while let Some(inside) = rest.strip_prefix('{') {
let Some(end) = inside.find('}') else {
return Err(format!("'{text}' opens a brace it does not close"));
};
match inside[..end].trim() {
"z" => mask.zero = true,
name => match name.strip_prefix("%k").and_then(|number| number.parse::<u8>().ok()) {
Some(number @ 1..=7) => mask.k = number,
_ => {
return Err(format!(
"'{name}' is not a mask an instruction can be written under"
));
}
},
}
rest = inside[end + 1..].trim_start();
}
if !rest.is_empty() || mask.k == 0 {
return Err(format!("'{text}' is not a mask an instruction can be written under"));
}
Ok((text[..cut].trim_end(), Some(mask)))
}
fn value(operand: &Operand, standing: i64) -> Value {
match operand {
Operand::Reg(reg, width) => Value::Reg(*reg, *width),
Operand::High(reg) => Value::High(*reg),
Operand::Xmm(reg) => Value::Xmm(*reg),
Operand::Vector(number, length) => Value::Vector(*number, *length),
Operand::Mask(number) => Value::Mask(*number),
Operand::Stack(_) => Value::Stack,
Operand::Mem(addr, _) => Value::Mem(*addr),
Operand::Imm(number) => Value::Imm(*number),
Operand::Expr(_) => Value::Imm(standing),
Operand::Dest(_) => Value::Dest,
}
}
fn operand(text: &str) -> Result<Operand, String> {
if text.is_empty() {
return Err("an operand with nothing in it".to_owned());
}
if let Some(rest) = text.strip_prefix('*') {
return match operand(rest.trim())? {
it @ (Operand::Reg(_, _) | Operand::Mem(_, _)) => Ok(it),
_ => Err(format!("'{text}' goes through something that is not a place")),
};
}
if let Some(rest) = text.strip_prefix('$') {
return Ok(number(rest.trim())
.map_or_else(|_| Operand::Expr(rest.trim().to_owned()), Operand::Imm));
}
if let Some(depth) = stack(text) {
return Ok(Operand::Stack(depth));
}
if text.starts_with('%') && !text.contains('(') && !text.contains(':') {
return register(&text[1..]);
}
if text.starts_with('%') || text.contains('(') {
return address(text);
}
if text.chars().all(|ch| ch.is_alphanumeric() || matches!(ch, '_' | '.' | '$' | '@')) {
return Ok(Operand::Dest(Named { name: text.to_owned(), addend: 0 }));
}
if let Ok((addend, Some(name))) = parted(text) {
return Ok(Operand::Dest(Named { name, addend }));
}
Err(format!("'{text}' is not an operand this compiler reads"))
}
fn stack(text: &str) -> Option<u8> {
let rest = text.strip_prefix("%st")?;
if rest.is_empty() {
return Some(0);
}
let depth = rest.strip_prefix('(')?.strip_suffix(')')?.trim().parse::<u8>().ok()?;
(depth < 8).then_some(depth)
}
fn depths(mnemonic: &str) -> &'static [u8] {
match mnemonic {
"faddp" | "fsubp" | "fsubrp" | "fmulp" | "fdivp" | "fdivrp" => &[0, 1],
"fucomip" => &[1, 0],
"fstp" => &[0],
_ => &[],
}
}
fn register(name: &str) -> Result<Operand, String> {
if let Some((reg, width)) = gpr_named(name) {
return Ok(Operand::Reg(reg, width));
}
if let Some(number) = ["ah", "ch", "dh", "bh"].iter().position(|&known| known == name) {
return Ok(Operand::High(PhysReg::new(number as u8)));
}
if let Some(rest) = name.strip_prefix("xmm") {
if let Ok(number) = rest.parse::<u8>() {
if number < 16 {
return Ok(Operand::Xmm(PhysReg::new(number)));
}
}
}
for (prefix, length) in [("xmm", Length::Xmm), ("ymm", Length::Ymm), ("zmm", Length::Zmm)] {
let Some(rest) = name.strip_prefix(prefix) else { continue };
if let Ok(number) = rest.parse::<u8>() {
if number < 32 && (rest == "0" || !rest.starts_with('0')) {
return Ok(Operand::Vector(number, length));
}
}
}
if let Some(Ok(number)) = name.strip_prefix('k').map(str::parse::<u8>) {
if number < 8 {
return Ok(Operand::Mask(number));
}
}
Err(format!("'%{name}' is not a register this compiler has"))
}
fn address(text: &str) -> Result<Operand, String> {
let mut rest = text;
let mut addr = Addr { scale: 1, ..Addr::default() };
if let Some(cut) = rest.find(':') {
let name = rest[..cut].trim();
addr.segment = Some(match name {
"%fs" => Segment::Fs,
"%gs" => Segment::Gs,
_ => return Err(format!("'{name}' is not a segment this machine reaches through")),
});
rest = rest[cut + 1..].trim();
}
let (front, inside) = match rest.find('(') {
Some(cut) => {
let Some(end) = rest.rfind(')') else {
return Err(format!("'{text}' opens a bracket and does not close it"));
};
if end < cut || rest[end + 1..].trim() != "" {
return Err(format!("'{text}' is not an address this compiler reads"));
}
(rest[..cut].trim(), Some(rest[cut + 1..end].trim()))
}
None => (rest.trim(), None),
};
let mut named = None;
if !front.is_empty() {
let (value, name) = parted(front)?;
match name {
Some(name) => named = Some(Named { name, addend: value }),
None => {
addr.disp = i32::try_from(value).map_err(|_| {
format!("'{front}' does not fit in the four bytes of an address")
})?;
}
}
}
let parts: Vec<&str> = inside.map_or_else(Vec::new, |inside| {
if inside.is_empty() { Vec::new() } else { inside.split(',').map(str::trim).collect() }
});
if parts.len() > 3 {
return Err(format!("'{text}' has more than a base, an index and a scale in it"));
}
if let Some(base) = parts.first().filter(|base| !base.is_empty()) {
if *base == "%rip" {
addr.rip = true;
} else {
addr.base = Some(whole(base)?);
}
}
if let Some(index) = parts.get(1).filter(|index| !index.is_empty()) {
addr.index = Some(whole(index)?);
}
if let Some(scale) = parts.get(2).filter(|scale| !scale.is_empty()) {
let by = number(scale)?;
if !matches!(by, 1 | 2 | 4 | 8) {
return Err(format!("{by} is not a scale this machine has"));
}
addr.scale = u8::try_from(by).unwrap_or(1);
}
if named.is_some() && !addr.rip {
return Err(format!(
"'{text}' names something in an address that is not counted from the instruction, \
which wants a relocation this compiler does not write yet"
));
}
if !addr.rip && addr.base.is_none() && addr.index.is_none() && named.is_none() {
}
Ok(Operand::Mem(addr, named))
}
fn whole(text: &str) -> Result<PhysReg, String> {
let Some(name) = text.strip_prefix('%') else {
return Err(format!("'{text}' is not a register"));
};
match gpr_named(name) {
Some((reg, Width::Quad)) => Ok(reg),
Some((_, width)) => Err(format!(
"'%{name}' is {} bits, and an address on this machine is made of whole registers",
width.bits()
)),
None => Err(format!("'%{name}' is not a register this compiler has")),
}
}
fn parted(text: &str) -> Result<(i64, Option<String>), String> {
let text = text.trim();
if let Some(value) = crate::source::constant(text) {
return Ok((value, None));
}
let mut total: i64 = 0;
let mut sign: i64 = 1;
let mut start = 0usize;
let mut named: Option<String> = None;
let reckon = |term: &str| number(term).or_else(|why| crate::source::constant(term).ok_or(why));
let mut fold = |term: &str, sign: i64, named: &mut Option<String>| match reckon(term) {
Ok(value) => {
total = total.wrapping_add(sign.wrapping_mul(value));
Ok(())
}
Err(why) => {
if named.is_some() {
return Err(
"two names added together, which is not a place a linker can find".to_owned()
);
}
if sign < 0 {
return Err(why);
}
*named = Some(term.trim().to_owned());
Ok(())
}
};
for (at, ch) in text.char_indices() {
if at == start || !matches!(ch, '+' | '-') {
continue;
}
fold(&text[start..at], sign, &mut named)?;
sign = if ch == '-' { -1 } else { 1 };
start = at + 1;
}
fold(&text[start..], sign, &mut named)?;
Ok((total, named))
}
fn number(text: &str) -> Result<i64, String> {
let text = text.trim();
let (sign, digits) = match text.strip_prefix('-') {
Some(rest) => (-1i64, rest.trim()),
None => (1, text.strip_prefix('+').map_or(text, str::trim)),
};
let value =
if let Some(hex) = digits.strip_prefix("0x").or_else(|| digits.strip_prefix("0X")) {
u64::from_str_radix(hex, 16)
} else if digits.len() > 1 && digits.starts_with('0') {
u64::from_str_radix(&digits[1..], 8)
} else {
digits.parse::<u64>()
}
.map(|value| value as i64);
value.map(|value| sign.wrapping_mul(value)).map_err(|_| format!("'{text}' is not a number"))
}
#[cfg(test)]
mod tests {
use super::*;
fn bytes(line: &str) -> Vec<u8> {
let (word, rest) = line.split_once(char::is_whitespace).unwrap_or((line, ""));
let args: Vec<String> =
if rest.trim().is_empty() { Vec::new() } else { crate::source::split(rest, ',') };
match one(word, &args) {
Ok(written) => {
assert!(written.holes.is_empty(), "this one names something: {:?}", written.holes);
written.bytes
}
Err(why) => panic!("{line}: {why}"),
}
}
fn refused(line: &str) -> String {
let (word, rest) = line.split_once(char::is_whitespace).unwrap_or((line, ""));
let args: Vec<String> =
if rest.trim().is_empty() { Vec::new() } else { crate::source::split(rest, ',') };
one(word, &args)
.err()
.unwrap_or_else(|| panic!("'{line}' was read and should not have been"))
}
#[test]
fn the_most_negative_number_is_a_number() {
assert_eq!(
bytes("movabsq $-9223372036854775808, %rax"),
[0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0x80]
);
}
#[test]
fn a_conditional_jump_counted_from_itself_is_short_when_it_fits() {
assert_eq!(bytes("jne .+2"), [0x75, 0x00]);
assert_eq!(bytes("jmp .+1000"), [0xe9, 0xe3, 0x03, 0x00, 0x00]);
}
#[test]
fn the_x87_stack_at_the_depths_the_compiler_writes_it() {
assert_eq!(bytes("fucomip %st(1), %st(0)"), [0xdf, 0xe9]);
assert_eq!(bytes("fstp %st(0)"), [0xdd, 0xd8]);
assert_eq!(bytes("fstp %st"), [0xdd, 0xd8]);
assert_eq!(bytes("faddp %st(0), %st(1)"), [0xde, 0xc1]);
assert_eq!(bytes("fstp %st(1)"), [0xdd, 0xd9]);
assert_eq!(bytes("faddp %st, %st(3)"), [0xde, 0xc3]);
assert_eq!(bytes("fucomip %st(2), %st"), [0xdf, 0xea]);
assert!(refused("faddp %st(1), %st(2)").contains("depths"));
assert_eq!(bytes("fprem"), [0xd9, 0xf8]);
assert_eq!(bytes("fnstsw %ax"), [0xdf, 0xe0]);
assert!(refused("fnstsw %bx").contains("ax"));
}
#[test]
fn a_move_between_registers() {
assert_eq!(bytes("movq %rdi, %rax"), vec![0x48, 0x89, 0xf8]);
assert_eq!(bytes("movl %edi, %eax"), vec![0x89, 0xf8]);
}
#[test]
fn the_width_letter_the_operands_already_said() {
assert_eq!(bytes("mov %rdi, %rax"), bytes("movq %rdi, %rax"));
assert_eq!(bytes("mov %edi, %eax"), bytes("movl %edi, %eax"));
assert_eq!(bytes("and %rdx, %rcx"), bytes("andq %rdx, %rcx"));
}
#[test]
fn the_other_name_of_a_condition_is_the_same_instruction() {
assert_eq!(bytes("setc %al"), bytes("setb %al"));
assert_eq!(bytes("cmovz %rdx, %rax"), bytes("cmove %rdx, %rax"));
assert_eq!(bytes("cmovnzq %rdx, %rax"), bytes("cmovneq %rdx, %rax"));
}
#[test]
fn a_mnemonic_that_ends_in_a_letter_that_is_also_a_width() {
assert_eq!(bytes("seta %al"), vec![0x0f, 0x97, 0xc0]);
}
#[test]
fn operands_that_disagree_about_the_width_are_refused() {
let why = refused("mov %eax, %rbx");
assert!(why.contains("32 bits") && why.contains("64 bits"), "{why}");
}
#[test]
fn a_number_on_the_instruction() {
assert_eq!(bytes("subq $24, %rsp"), vec![0x48, 0x83, 0xec, 0x18]);
assert_eq!(bytes("subq $4096, %rsp"), vec![0x48, 0x81, 0xec, 0x00, 0x10, 0x00, 0x00]);
}
#[test]
fn the_three_ways_a_file_writes_a_number() {
assert_eq!(bytes("addq $0x10, %rax"), bytes("addq $16, %rax"));
assert_eq!(bytes("addq $020, %rax"), bytes("addq $16, %rax"));
assert_eq!(bytes("addq $-1, %rax"), vec![0x48, 0x83, 0xc0, 0xff]);
}
#[test]
fn an_address_with_everything_in_it() {
assert_eq!(bytes("movq 8(%rbp), %rax"), vec![0x48, 0x8b, 0x45, 0x08]);
assert_eq!(bytes("movq (%rax), %rbx"), vec![0x48, 0x8b, 0x18]);
assert_eq!(bytes("movq 16(%rsi,%rdi,8), %rax"), vec![0x48, 0x8b, 0x44, 0xfe, 0x10]);
}
#[test]
fn a_store_and_a_load_are_different_instructions_under_one_mnemonic() {
assert_eq!(bytes("movq %rbx, 0(%rsp)"), vec![0x48, 0x89, 0x1c, 0x24]);
assert_ne!(bytes("movq %rbx, 0(%rsp)"), bytes("movq 0(%rsp), %rbx"));
}
#[test]
fn the_segment_a_thread_keeps_its_own_block_in() {
assert_eq!(bytes("movq %fs:40, %rax"), vec![0x64, 0x48, 0x8b, 0x04, 0x25, 40, 0, 0, 0]);
}
#[test]
fn a_name_counted_from_the_end_of_the_instruction_is_a_hole() {
let written = one("movq", &["message(%rip)".to_owned(), "%rax".to_owned()]).expect("read");
assert_eq!(written.holes.len(), 1);
assert_eq!(written.holes[0].name, "message");
assert_eq!(written.holes[0].sort, Sort::Near);
assert_eq!(written.holes[0].at, written.bytes.len() - 4);
}
#[test]
fn a_number_counted_from_the_end_of_the_instruction_is_not_one() {
let written = one("movq", &["8(%rip)".to_owned(), "%rax".to_owned()]).expect("read");
assert!(written.holes.is_empty(), "{:?}", written.holes);
}
#[test]
fn somewhere_to_go_is_a_hole_whatever_kind_of_branch_it_is() {
for line in ["jmp there", "je there", "jnz there", "call there"] {
let (word, rest) = line.split_once(' ').expect("two words");
let written = one(word, &[rest.to_owned()]).expect("read");
assert_eq!(written.holes.len(), 1, "{line}");
assert_eq!(written.holes[0].name, "there", "{line}");
assert_eq!(written.holes[0].sort, Sort::Branch, "{line}");
assert_eq!(written.holes[0].width, 4, "{line}");
assert_eq!(written.holes[0].at, written.bytes.len() - 4, "{line}");
}
}
#[test]
fn the_one_branch_that_leaves_a_byte_says_a_byte() {
let written = one("jrcxz", &["there".to_owned()]).expect("read");
assert_eq!(written.bytes, vec![0xe3, 0x00]);
assert_eq!(written.holes.len(), 1);
assert_eq!(written.holes[0].width, 1);
assert_eq!(written.holes[0].sort, Sort::Branch);
assert_eq!(written.holes[0].at, 1);
}
#[test]
fn the_instructions_a_hand_written_file_writes_without_a_width_letter() {
assert_eq!(bytes("adc (%rdx), %r8"), vec![0x4c, 0x13, 0x02]);
assert_eq!(bytes("adc %eax, %eax"), vec![0x11, 0xc0]);
assert_eq!(bytes("bt $0, %r8"), vec![0x49, 0x0f, 0xba, 0xe0, 0x00]);
assert_eq!(bytes("dec %rcx"), vec![0x48, 0xff, 0xc9]);
assert_eq!(bytes("inc %eax"), vec![0xff, 0xc0]);
assert_eq!(bytes("lea 32(%rsi), %rsi"), vec![0x48, 0x8d, 0x76, 0x20]);
assert_eq!(bytes("setc %al"), vec![0x0f, 0x92, 0xc0]);
}
#[test]
fn a_line_gas_writes_shorter_is_written_shorter_here_too() {
assert_eq!(bytes("shl $1, %eax"), [0xd1, 0xe0]);
assert_eq!(bytes("sarq $1, %rdx"), [0x48, 0xd1, 0xfa]);
assert_eq!(bytes("shrb $1, %r9b"), [0x41, 0xd0, 0xe9]);
assert_eq!(bytes("shl $2, %eax"), [0xc1, 0xe0, 0x02]);
assert_eq!(bytes("cmp $1000000, %eax"), [0x3d, 0x40, 0x42, 0x0f, 0x00]);
assert_eq!(bytes("addq $4096, %rax"), [0x48, 0x05, 0x00, 0x10, 0x00, 0x00]);
assert_eq!(bytes("andw $4095, %ax"), [0x66, 0x25, 0xff, 0x0f]);
assert_eq!(bytes("xorb $15, %al"), [0x34, 0x0f]);
assert_eq!(bytes("test $256, %eax"), [0xa9, 0x00, 0x01, 0x00, 0x00]);
assert_eq!(bytes("testb $1, %al"), [0xa8, 0x01]);
assert_eq!(bytes("cmp $1, %eax"), [0x83, 0xf8, 0x01]);
assert_eq!(bytes("cmp $1000000, %ecx"), [0x81, 0xf9, 0x40, 0x42, 0x0f, 0x00]);
assert_eq!(bytes("cmp $1000000, %r8d"), [0x41, 0x81, 0xf8, 0x40, 0x42, 0x0f, 0x00]);
}
#[test]
fn shifting_left_arithmetically_is_shifting_left_and_the_table_knows_one_name_for_it() {
assert_eq!(bytes("sal $11, %eax"), bytes("shl $11, %eax"));
assert_eq!(bytes("salq $1, %rdx"), bytes("shlq $1, %rdx"));
assert_eq!(bytes("sal %cl, %rax"), bytes("shl %cl, %rax"));
}
#[test]
fn the_integer_forms_gcc_writes_with_memory_in_them() {
assert_eq!(bytes("sall -4(%rbp)"), [0xd1, 0x65, 0xfc]);
assert_eq!(bytes("shrl $1, -4(%rbp)"), [0xd1, 0x6d, 0xfc]);
assert_eq!(bytes("shrq %cl, -8(%rbp)"), [0x48, 0xd3, 0x6d, 0xf8]);
assert_eq!(bytes("shrw $8, 16(%rdi)"), [0x66, 0xc1, 0x6f, 0x10, 0x08]);
assert_eq!(bytes("roll $13, -4(%rbp)"), [0xc1, 0x45, 0xfc, 0x0d]);
assert_eq!(bytes("sete 78(%rsp)"), [0x0f, 0x94, 0x44, 0x24, 0x4e]);
assert_eq!(bytes("pushq $112"), [0x6a, 0x70]);
assert_eq!(bytes("pushq $1000"), [0x68, 0xe8, 0x03, 0, 0]);
assert_eq!(bytes("imull $-1640531535, (%rsi), %eax"), [0x69, 0x06, 0xb1, 0x79, 0x37, 0x9e]);
assert_eq!(bytes("imulq $40, 8(%rsp), %rax"), [0x48, 0x6b, 0x44, 0x24, 0x08, 0x28]);
}
#[test]
fn a_comparison_named_for_its_predicate_is_the_one_with_an_immediate() {
assert_eq!(bytes("cmpnlesd %xmm0, %xmm2"), [0xf2, 0x0f, 0xc2, 0xd0, 0x06]);
assert_eq!(bytes("cmpltss (%rax), %xmm1"), [0xf3, 0x0f, 0xc2, 0x08, 0x01]);
assert_eq!(bytes("cmpnlesd %xmm0, %xmm2"), bytes("cmpsd $6, %xmm0, %xmm2"));
}
#[test]
fn a_bare_number_is_an_address_outright() {
assert_eq!(bytes("movq %rax, 0"), [0x48, 0x89, 0x04, 0x25, 0, 0, 0, 0]);
assert_eq!(bytes("movl %eax, 8"), [0x89, 0x04, 0x25, 0x08, 0, 0, 0]);
}
#[test]
fn a_count_in_a_byte_register_says_nothing_about_how_wide_the_shift_is() {
assert_eq!(bytes("shr %cl, %rax"), bytes("shrq %cl, %rax"));
assert_eq!(bytes("shl %cl, %edx"), bytes("shll %cl, %edx"));
assert_eq!(bytes("rcr %cl, %rbx"), bytes("rcrq %cl, %rbx"));
assert_eq!(bytes("shld %cl, %rsi, %rdi"), bytes("shldq %cl, %rsi, %rdi"));
}
#[test]
fn a_shift_that_takes_its_count_anywhere_says_its_width_the_ordinary_way() {
assert_eq!(bytes("shlx %rdx, %rax, %rax"), bytes("shlxq %rdx, %rax, %rax"));
assert_eq!(bytes("shrx %rdx, %rax, %rax"), bytes("shrxq %rdx, %rax, %rax"));
assert_eq!(bytes("sarx %edx, %eax, %eax"), bytes("sarxl %edx, %eax, %eax"));
assert_eq!(bytes("shrxq %r8, %rax, %rdx"), vec![0xc4, 0xe2, 0xbb, 0xf7, 0xd0]);
assert_eq!(bytes("shlx %r15, %rax, %rax"), vec![0xc4, 0xe2, 0x81, 0xf7, 0xc0]);
let why = refused("shlx %cl, %rax, %rax");
assert!(why.contains("8 bits") && why.contains("64 bits"), "{why}");
}
#[test]
fn a_displacement_that_is_written_as_a_sum_is_the_sum() {
assert_eq!(bytes("movl 56+8(%rsp), %ecx"), bytes("movl 64(%rsp), %ecx"));
assert_eq!(bytes("lea -512+128(%rsp), %rdi"), bytes("lea -384(%rsp), %rdi"));
assert_eq!(bytes("movq 8+8+8(%rdi), %rax"), bytes("movq 24(%rdi), %rax"));
assert_eq!(bytes("movq 32-8(%rdi), %rax"), bytes("movq 24(%rdi), %rax"));
assert_eq!(bytes("movdqu 80+0*16(%rdi), %xmm1"), bytes("movdqu 80(%rdi), %xmm1"));
assert_eq!(bytes("movq 1*8(%r12), %rax"), bytes("movq 8(%r12), %rax"));
assert_eq!(bytes("movq -2*8(%rsp), %rax"), bytes("movq -16(%rsp), %rax"));
assert_eq!(bytes("movq 1<<4(%rdi), %rax"), bytes("movq 16(%rdi), %rax"));
let read = |arg: &str| one("leaq", &[arg.to_owned(), "%rax".to_owned()]).expect("read");
let product = read("K256+8*16(%rip)");
let sum = read("K256+128(%rip)");
assert_eq!(product.bytes, sum.bytes);
assert_eq!(product.holes[0].name, "K256");
assert_eq!(product.holes[0].addend, sum.holes[0].addend);
}
#[test]
fn the_ssse3_sse41_and_sha_instructions_hand_written_code_uses_are_read() {
assert_eq!(bytes("pshufb %xmm7, %xmm0"), [0x66, 0x0f, 0x38, 0x00, 0xc7]);
assert_eq!(bytes("palignr $4, %xmm3, %xmm7"), [0x66, 0x0f, 0x3a, 0x0f, 0xfb, 0x04]);
assert_eq!(bytes("pinsrd $3, 80(%rdi), %xmm1"), [0x66, 0x0f, 0x3a, 0x22, 0x4f, 0x50, 0x03]);
assert_eq!(bytes("pextrd $3, %xmm1, 80(%rdi)"), [0x66, 0x0f, 0x3a, 0x16, 0x4f, 0x50, 0x03]);
assert_eq!(bytes("pextrd $1, %xmm2, %eax"), [0x66, 0x0f, 0x3a, 0x16, 0xd0, 0x01]);
assert_eq!(bytes("pinsrq $1, %rax, %xmm9"), [0x66, 0x4c, 0x0f, 0x3a, 0x22, 0xc8, 0x01]);
assert_eq!(bytes("sha1rnds4 $1, %xmm2, %xmm0"), [0x0f, 0x3a, 0xcc, 0xc2, 0x01]);
assert_eq!(bytes("sha1nexte %xmm3, %xmm1"), [0x0f, 0x38, 0xc8, 0xcb]);
assert_eq!(bytes("sha1msg2 %xmm6, %xmm3"), [0x0f, 0x38, 0xca, 0xde]);
assert_eq!(bytes("sha256msg1 %xmm4, %xmm3"), [0x0f, 0x38, 0xcc, 0xdc]);
assert_eq!(bytes("sha256rnds2 %xmm0, %xmm1, %xmm2"), [0x0f, 0x38, 0xcb, 0xd1]);
assert_eq!(bytes("sha256rnds2 %xmm1, %xmm2"), [0x0f, 0x38, 0xcb, 0xd1]);
assert!(one("sha256rnds2", &["%xmm3".into(), "%xmm1".into(), "%xmm2".into()]).is_err());
}
#[test]
fn the_sse3_to_sse4_2_instructions_the_intrinsics_write_are_read() {
let table: &[(&str, &[u8])] = &[
("addsubps %xmm2, %xmm9", &[0xf2, 0x44, 0x0f, 0xd0, 0xca]),
("addsubps 16(%rdi), %xmm1", &[0xf2, 0x0f, 0xd0, 0x4f, 0x10]),
("addsubpd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0xd0, 0xca]),
("addsubpd 16(%rdi), %xmm1", &[0x66, 0x0f, 0xd0, 0x4f, 0x10]),
("haddps %xmm2, %xmm9", &[0xf2, 0x44, 0x0f, 0x7c, 0xca]),
("haddps 16(%rdi), %xmm1", &[0xf2, 0x0f, 0x7c, 0x4f, 0x10]),
("haddpd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x7c, 0xca]),
("haddpd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x7c, 0x4f, 0x10]),
("hsubps %xmm2, %xmm9", &[0xf2, 0x44, 0x0f, 0x7d, 0xca]),
("hsubps 16(%rdi), %xmm1", &[0xf2, 0x0f, 0x7d, 0x4f, 0x10]),
("hsubpd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x7d, 0xca]),
("hsubpd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x7d, 0x4f, 0x10]),
("movshdup %xmm2, %xmm9", &[0xf3, 0x44, 0x0f, 0x16, 0xca]),
("movshdup 16(%rdi), %xmm1", &[0xf3, 0x0f, 0x16, 0x4f, 0x10]),
("movsldup %xmm2, %xmm9", &[0xf3, 0x44, 0x0f, 0x12, 0xca]),
("movsldup 16(%rdi), %xmm1", &[0xf3, 0x0f, 0x12, 0x4f, 0x10]),
("movddup %xmm2, %xmm9", &[0xf2, 0x44, 0x0f, 0x12, 0xca]),
("movddup 16(%rdi), %xmm1", &[0xf2, 0x0f, 0x12, 0x4f, 0x10]),
("phaddw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x01, 0xca]),
("phaddw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x01, 0x4f, 0x10]),
("phaddd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x02, 0xca]),
("phaddd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x02, 0x4f, 0x10]),
("phaddsw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x03, 0xca]),
("phaddsw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x03, 0x4f, 0x10]),
("pmaddubsw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x04, 0xca]),
("pmaddubsw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x04, 0x4f, 0x10]),
("phsubw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x05, 0xca]),
("phsubw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x05, 0x4f, 0x10]),
("phsubd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x06, 0xca]),
("phsubd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x06, 0x4f, 0x10]),
("phsubsw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x07, 0xca]),
("phsubsw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x07, 0x4f, 0x10]),
("psignb %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x08, 0xca]),
("psignb 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x08, 0x4f, 0x10]),
("psignw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x09, 0xca]),
("psignw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x09, 0x4f, 0x10]),
("psignd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x0a, 0xca]),
("psignd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x0a, 0x4f, 0x10]),
("pmulhrsw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x0b, 0xca]),
("pmulhrsw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x0b, 0x4f, 0x10]),
("pabsb %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x1c, 0xca]),
("pabsb 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x1c, 0x4f, 0x10]),
("pabsw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x1d, 0xca]),
("pabsw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x1d, 0x4f, 0x10]),
("pabsd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x1e, 0xca]),
("pabsd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x1e, 0x4f, 0x10]),
("pblendvb %xmm0, %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x10, 0xca]),
("pblendvb %xmm0, 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x10, 0x4f, 0x10]),
("blendvps %xmm0, %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x14, 0xca]),
("blendvps %xmm0, 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x14, 0x4f, 0x10]),
("blendvpd %xmm0, %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x15, 0xca]),
("blendvpd %xmm0, 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x15, 0x4f, 0x10]),
("ptest %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x17, 0xca]),
("ptest 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x17, 0x4f, 0x10]),
("pmovsxbw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x20, 0xca]),
("pmovsxbw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x20, 0x4f, 0x10]),
("pmovsxbd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x21, 0xca]),
("pmovsxbd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x21, 0x4f, 0x10]),
("pmovsxbq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x22, 0xca]),
("pmovsxbq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x22, 0x4f, 0x10]),
("pmovsxwd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x23, 0xca]),
("pmovsxwd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x23, 0x4f, 0x10]),
("pmovsxwq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x24, 0xca]),
("pmovsxwq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x24, 0x4f, 0x10]),
("pmovsxdq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x25, 0xca]),
("pmovsxdq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x25, 0x4f, 0x10]),
("pmuldq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x28, 0xca]),
("pmuldq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x28, 0x4f, 0x10]),
("pcmpeqq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x29, 0xca]),
("pcmpeqq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x29, 0x4f, 0x10]),
("packusdw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x2b, 0xca]),
("packusdw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x2b, 0x4f, 0x10]),
("pmovzxbw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x30, 0xca]),
("pmovzxbw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x30, 0x4f, 0x10]),
("pmovzxbd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x31, 0xca]),
("pmovzxbd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x31, 0x4f, 0x10]),
("pmovzxbq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x32, 0xca]),
("pmovzxbq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x32, 0x4f, 0x10]),
("pmovzxwd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x33, 0xca]),
("pmovzxwd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x33, 0x4f, 0x10]),
("pmovzxwq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x34, 0xca]),
("pmovzxwq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x34, 0x4f, 0x10]),
("pmovzxdq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x35, 0xca]),
("pmovzxdq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x35, 0x4f, 0x10]),
("pcmpgtq %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x37, 0xca]),
("pcmpgtq 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x37, 0x4f, 0x10]),
("pminsb %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x38, 0xca]),
("pminsb 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x38, 0x4f, 0x10]),
("pminsd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x39, 0xca]),
("pminsd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x39, 0x4f, 0x10]),
("pminuw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x3a, 0xca]),
("pminuw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x3a, 0x4f, 0x10]),
("pminud %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x3b, 0xca]),
("pminud 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x3b, 0x4f, 0x10]),
("pmaxsb %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x3c, 0xca]),
("pmaxsb 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x3c, 0x4f, 0x10]),
("pmaxsd %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x3d, 0xca]),
("pmaxsd 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x3d, 0x4f, 0x10]),
("pmaxuw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x3e, 0xca]),
("pmaxuw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x3e, 0x4f, 0x10]),
("pmaxud %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x3f, 0xca]),
("pmaxud 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x3f, 0x4f, 0x10]),
("pmulld %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x40, 0xca]),
("pmulld 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x40, 0x4f, 0x10]),
("phminposuw %xmm2, %xmm9", &[0x66, 0x44, 0x0f, 0x38, 0x41, 0xca]),
("phminposuw 16(%rdi), %xmm1", &[0x66, 0x0f, 0x38, 0x41, 0x4f, 0x10]),
("roundps $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x08, 0xda, 0x05]),
("roundps $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x08, 0x08, 0x01]),
("roundpd $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x09, 0xda, 0x05]),
("roundpd $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x09, 0x08, 0x01]),
("roundss $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x0a, 0xda, 0x05]),
("roundss $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x0a, 0x08, 0x01]),
("roundsd $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x0b, 0xda, 0x05]),
("roundsd $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x0b, 0x08, 0x01]),
("blendps $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x0c, 0xda, 0x05]),
("blendps $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x0c, 0x08, 0x01]),
("blendpd $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x0d, 0xda, 0x05]),
("blendpd $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x0d, 0x08, 0x01]),
("pblendw $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x0e, 0xda, 0x05]),
("pblendw $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x0e, 0x08, 0x01]),
("insertps $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x21, 0xda, 0x05]),
("insertps $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x21, 0x08, 0x01]),
("dpps $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x40, 0xda, 0x05]),
("dpps $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x40, 0x08, 0x01]),
("dppd $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x41, 0xda, 0x05]),
("dppd $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x41, 0x08, 0x01]),
("mpsadbw $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x42, 0xda, 0x05]),
("mpsadbw $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x42, 0x08, 0x01]),
("pcmpestrm $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x60, 0xda, 0x05]),
("pcmpestrm $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x60, 0x08, 0x01]),
("pcmpestri $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x61, 0xda, 0x05]),
("pcmpestri $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x61, 0x08, 0x01]),
("pcmpistrm $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x62, 0xda, 0x05]),
("pcmpistrm $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x62, 0x08, 0x01]),
("pcmpistri $5, %xmm10, %xmm3", &[0x66, 0x41, 0x0f, 0x3a, 0x63, 0xda, 0x05]),
("pcmpistri $1, (%r8), %xmm1", &[0x66, 0x41, 0x0f, 0x3a, 0x63, 0x08, 0x01]),
("lddqu (%rsi), %xmm12", &[0xf2, 0x44, 0x0f, 0xf0, 0x26]),
("movntdqa (%rsi), %xmm12", &[0x66, 0x44, 0x0f, 0x38, 0x2a, 0x26]),
("extractps $2, %xmm1, %eax", &[0x66, 0x0f, 0x3a, 0x17, 0xc8, 0x02]),
("extractps $3, %xmm9, %r10d", &[0x66, 0x45, 0x0f, 0x3a, 0x17, 0xca, 0x03]),
("extractps $1, %xmm2, 8(%rdi)", &[0x66, 0x0f, 0x3a, 0x17, 0x57, 0x08, 0x01]),
];
for &(line, want) in table {
assert_eq!(bytes(line), want, "{line}");
}
assert_eq!(bytes("pblendvb %xmm2, %xmm9"), bytes("pblendvb %xmm0, %xmm2, %xmm9"));
assert!(refused("blendvps %xmm1, %xmm2, %xmm3").contains("xmm0"));
}
#[test]
fn a_name_reached_through_the_global_offset_table_says_which_kind_of_hole_it_is() {
let arg = "table@GOTPCREL(%rip)".to_owned();
let written = one("movq", &[arg, "%rdx".to_owned()]).expect("read");
assert_eq!(written.holes.len(), 1);
assert_eq!(written.holes[0].name, "table");
assert_eq!(written.holes[0].sort, Sort::Table);
assert_eq!(written.holes[0].at, written.bytes.len() - 4);
let arg = "counter@GOTTPOFF(%rip)".to_owned();
let written = one("movq", &[arg, "%rax".to_owned()]).expect("read");
assert_eq!(written.holes[0].name, "counter");
assert_eq!(written.holes[0].sort, Sort::Thread);
let why = refused("movq away@TPOFF(%rip), %rax");
assert!(why.contains("@TPOFF"), "{why}");
}
#[test]
fn a_call_through_a_stub_is_the_relocation_a_call_already_gets() {
let written = one("call", &["work@PLT".to_owned()]).expect("read");
assert_eq!(written.holes.len(), 1);
assert_eq!(written.holes[0].name, "work");
assert_eq!(written.holes[0].sort, Sort::Branch);
assert_eq!(written.bytes, one("call", &["work".to_owned()]).expect("read").bytes);
let why = refused("call work@GOTPCREL");
assert!(why.contains("@GOTPCREL"), "{why}");
}
#[test]
fn a_branch_through_a_register_is_a_different_instruction_and_names_nothing() {
let written = one("jmp", &["*%rax".to_owned()]).expect("read");
assert_eq!(written.bytes, vec![0xff, 0xe0]);
assert!(written.holes.is_empty());
}
#[test]
fn a_branch_through_a_table_is_the_same_instruction_with_an_address_in_it() {
let written = one("jmp", &["*72(%r8,%rsi,8)".to_owned()]).expect("read");
assert_eq!(written.bytes, vec![0x41, 0xff, 0x64, 0xf0, 0x48]);
assert!(written.holes.is_empty());
let written = one("call", &["*(%rax)".to_owned()]).expect("read");
assert_eq!(written.bytes, vec![0xff, 0x10]);
assert!(written.holes.is_empty());
}
#[test]
fn a_constant_written_straight_into_memory() {
assert_eq!(bytes("movq $0, -8(%rsp)"), vec![0x48, 0xc7, 0x44, 0x24, 0xf8, 0, 0, 0, 0]);
assert_eq!(bytes("movl $1, -8(%rsp)"), vec![0xc7, 0x44, 0x24, 0xf8, 1, 0, 0, 0]);
assert_eq!(bytes("movw $1, -8(%rsp)"), vec![0x66, 0xc7, 0x44, 0x24, 0xf8, 1, 0]);
assert_eq!(bytes("movb $1, -8(%rsp)"), vec![0xc6, 0x44, 0x24, 0xf8, 1]);
let why = refused("movq $0x1122334455, -8(%rsp)");
assert!(why.contains("movq"), "{why}");
}
#[test]
fn a_push_and_a_pop_need_no_letter_because_there_is_only_one_width_of_them() {
assert_eq!(bytes("pop 120(%rax)"), vec![0x8f, 0x40, 0x78]);
assert_eq!(bytes("push 120(%rcx)"), vec![0xff, 0x71, 0x78]);
assert_eq!(bytes("push %rbx"), bytes("pushq %rbx"));
assert_eq!(bytes("pushf"), vec![0x9c]);
assert_eq!(bytes("popf"), vec![0x9d]);
}
#[test]
fn an_x87_instruction_written_with_a_wait_in_front_of_it() {
assert_eq!(bytes("fnstcw -8(%rsp)"), vec![0xd9, 0x7c, 0x24, 0xf8]);
assert_eq!(bytes("fstcw -8(%rsp)"), vec![0x9b, 0xd9, 0x7c, 0x24, 0xf8]);
assert_eq!(bytes("fnstenv (%rcx)"), vec![0xd9, 0x31]);
assert_eq!(bytes("fstenv (%rcx)"), vec![0x9b, 0xd9, 0x31]);
assert_eq!(bytes("fninit"), vec![0xdb, 0xe3]);
assert_eq!(bytes("finit"), vec![0x9b, 0xdb, 0xe3]);
assert_eq!(bytes("fstcw (%r8)"), vec![0x9b, 0x41, 0xd9, 0x38]);
}
#[test]
fn an_instruction_with_no_operands() {
assert_eq!(bytes("ret"), vec![0xc3]);
assert_eq!(bytes("nop"), vec![0x90]);
}
#[test]
fn a_register_this_machine_does_not_have_is_refused() {
let why = refused("movq %rax, %r99");
assert!(why.contains("r99"), "{why}");
}
#[test]
fn an_address_made_of_a_register_that_is_not_whole_is_refused() {
let why = refused("movq (%eax), %rbx");
assert!(why.contains("32 bits"), "{why}");
}
#[test]
fn a_name_in_an_address_that_is_not_counted_from_the_instruction_is_refused() {
let why = refused("movq message(%rbx), %rax");
assert!(why.contains("relocation"), "{why}");
}
#[test]
fn a_scale_the_machine_does_not_have_is_refused() {
let why = refused("movq (%rsi,%rdi,3), %rax");
assert!(why.contains("scale"), "{why}");
}
#[test]
fn the_checksum_step_takes_its_letter_from_what_it_reads() {
assert_eq!(bytes("crc32 %sil, %eax"), [0xf2, 0x40, 0x0f, 0x38, 0xf0, 0xc6]);
assert_eq!(bytes("crc32 %cx, %eax"), [0x66, 0xf2, 0x0f, 0x38, 0xf1, 0xc1]);
assert_eq!(bytes("crc32 %edx, %eax"), [0xf2, 0x0f, 0x38, 0xf1, 0xc2]);
assert_eq!(bytes("crc32 %r9, %rax"), [0xf2, 0x49, 0x0f, 0x38, 0xf1, 0xc1]);
assert_eq!(bytes("crc32l (%rdi), %eax"), [0xf2, 0x0f, 0x38, 0xf1, 0x07]);
assert_eq!(bytes("popcnt %rdi, %rax"), [0xf3, 0x48, 0x0f, 0xb8, 0xc7]);
assert!(refused("crc32 (%rdi), %eax").contains("crc32"));
assert!(refused("crc32b %al, %rcx").contains("sixty four"));
assert!(refused("crc32 %al, %rcx").contains("sixty four"));
}
#[test]
fn an_instruction_this_compiler_has_no_bytes_for_is_refused_by_name() {
let why = refused("aesenc %xmm1, %xmm0");
assert!(why.contains("aesenc"), "{why}");
}
#[test]
fn the_avx512_instructions_the_intrinsics_write_are_read() {
let lines: &[(&str, &[u8])] = &[
("vmovdqu64 (%rdi), %zmm16", &[0x62, 0xe1, 0xfe, 0x48, 0x6f, 0x07]),
("vmovdqu64 64(%rdi), %zmm17", &[0x62, 0xe1, 0xfe, 0x48, 0x6f, 0x4f, 0x01]),
("vmovdqu64 -128(%rsp), %zmm31", &[0x62, 0x61, 0xfe, 0x48, 0x6f, 0x7c, 0x24, 0xfe]),
("vmovdqu64 100(%rdi), %zmm16", &[0x62, 0xe1, 0xfe, 0x48, 0x6f, 0x87, 0x64, 0, 0, 0]),
(
"vmovdqu64 %zmm20, 64(%rsp,%rcx,8)",
&[0x62, 0xe1, 0xfe, 0x48, 0x7f, 0x64, 0xcc, 0x01],
),
("vmovdqu64 %zmm8, %zmm25", &[0x62, 0x41, 0xfe, 0x48, 0x6f, 0xc8]),
("vmovdqu64 (%r12), %zmm9", &[0x62, 0x51, 0xfe, 0x48, 0x6f, 0x0c, 0x24]),
("vmovdqu64 %xmm16, (%rax)", &[0x62, 0xe1, 0xfe, 0x08, 0x7f, 0x00]),
("vmovdqu64 %ymm16, 32(%rax)", &[0x62, 0xe1, 0xfe, 0x28, 0x7f, 0x40, 0x01]),
("vmovdqa64 %xmm1, %xmm18", &[0x62, 0xe1, 0xfd, 0x08, 0x6f, 0xd1]),
("vmovdqu8 (%rbx), %zmm16{%k1}{z}", &[0x62, 0xe1, 0x7f, 0xc9, 0x6f, 0x03]),
("vmovdqu8 (%r9,%r10), %zmm30{%k7}{z}", &[0x62, 0x01, 0x7f, 0xcf, 0x6f, 0x34, 0x11]),
("vmovdqu8 (%rbx), %zmm16{%k1}", &[0x62, 0xe1, 0x7f, 0x49, 0x6f, 0x03]),
("vmovdqu8 %zmm16, (%rbx){%k2}", &[0x62, 0xe1, 0x7f, 0x4a, 0x7f, 0x03]),
("kmovq %rax, %k1", &[0xc4, 0xe1, 0xfb, 0x92, 0xc8]),
("kmovq %r13, %k7", &[0xc4, 0xc1, 0xfb, 0x92, 0xfd]),
("kmovq %k3, %r9", &[0xc4, 0x61, 0xfb, 0x93, 0xcb]),
("vpaddq %zmm16, %zmm17, %zmm18", &[0x62, 0xa1, 0xf5, 0x40, 0xd4, 0xd0]),
("vpaddq 128(%rax), %zmm17, %zmm18", &[0x62, 0xe1, 0xf5, 0x40, 0xd4, 0x50, 0x02]),
("vpaddq %xmm1, %xmm2, %xmm3", &[0xc5, 0xe9, 0xd4, 0xd9]),
("vpaddq %ymm9, %ymm10, %ymm11", &[0xc4, 0x41, 0x2d, 0xd4, 0xd9]),
("vpandq 64(%rdx), %zmm17, %zmm18", &[0x62, 0xe1, 0xf5, 0x40, 0xdb, 0x52, 0x01]),
("vpxorq %xmm16, %xmm17, %xmm18", &[0x62, 0xa1, 0xf5, 0x00, 0xef, 0xd0]),
(
"vpternlogq $0x96, 64(%rax), %zmm17, %zmm18",
&[0x62, 0xe3, 0xf5, 0x40, 0x25, 0x50, 0x01, 0x96],
),
(
"vpternlogq $150, %xmm16, %xmm17, %xmm18",
&[0x62, 0xa3, 0xf5, 0x00, 0x25, 0xd0, 0x96],
),
("vpopcntq (%rax), %zmm17", &[0x62, 0xe2, 0xfd, 0x48, 0x55, 0x08]),
("vpclmulqdq $17, %zmm16, %zmm17, %zmm18", &[0x62, 0xa3, 0x75, 0x40, 0x44, 0xd0, 0x11]),
("vpclmulqdq $0, %xmm1, %xmm2, %xmm3", &[0xc4, 0xe3, 0x69, 0x44, 0xd9, 0x00]),
("vextracti32x4 $3, %zmm16, %xmm1", &[0x62, 0xe3, 0x7d, 0x48, 0x39, 0xc1, 0x03]),
(
"vextracti32x4 $2, %zmm16, 32(%rax)",
&[0x62, 0xe3, 0x7d, 0x48, 0x39, 0x40, 0x02, 0x02],
),
("vextracti64x4 $1, %zmm16, %ymm17", &[0x62, 0xa3, 0xfd, 0x48, 0x3b, 0xc1, 0x01]),
("vbroadcasti32x4 32(%rax), %zmm16", &[0x62, 0xe2, 0x7d, 0x48, 0x5a, 0x40, 0x02]),
("vpbroadcastb %eax, %zmm1", &[0x62, 0xf2, 0x7d, 0x48, 0x7a, 0xc8]),
(
"vshufi64x2 $0xb1, (%rax), %zmm16, %zmm17",
&[0x62, 0xe3, 0xfd, 0x40, 0x43, 0x08, 0xb1],
),
("vpshufd $0x4e, %zmm16, %zmm17", &[0x62, 0xa1, 0x7d, 0x48, 0x70, 0xc8, 0x4e]),
("vpshufd $0x4e, %xmm1, %xmm2", &[0xc5, 0xf9, 0x70, 0xd1, 0x4e]),
("vmovq %xmm16, %rax", &[0x62, 0xe1, 0xfd, 0x08, 0x7e, 0xc0]),
("vmovq %xmm1, %rax", &[0xc4, 0xe1, 0xf9, 0x7e, 0xc8]),
("vmovq %rax, %xmm16", &[0x62, 0xe1, 0xfd, 0x08, 0x6e, 0xc0]),
];
for (line, expected) in lines {
assert_eq!(bytes(line), *expected, "{line}");
}
}
#[test]
fn a_mask_that_is_not_one_is_refused() {
assert!(refused("vmovdqu8 (%rbx), %zmm16{%k0}").contains("mask"));
assert!(refused("vmovdqu8 (%rbx), %zmm16{%k8}").contains("mask"));
assert!(refused("vmovdqu8 (%rbx), %zmm16{%k1").contains("brace"));
assert!(refused("addq %rax, %rbx{%k1}").contains("mask"));
assert!(refused("paddq %xmm16, %xmm1").contains("argument"));
assert!(refused("vmovdqu64 %zmm32, %zmm1").contains("register"));
}
}