use rucc_target::x86_64::{Addr, Encoding, ImmSize, Value, Width, encode, encoding, gpr_named};
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,
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),
Mem(Addr, Option<Named>),
Imm(i64),
Dest(String),
}
pub(crate) fn one(word: &str, args: &[String]) -> Result<Written, String> {
let operands: Vec<Operand> =
args.iter().map(|arg| operand(arg.trim())).collect::<Result<_, _>>()?;
let values: Vec<Value> = operands.iter().map(value).collect();
let (mnemonic, row) = spelled(word, &operands, &values)?;
let mut bytes = Vec::with_capacity(16);
let holes = encode(&mnemonic, &values, &mut bytes).map_err(|why| why.to_string())?;
let mut wanted = Vec::new();
if let Some(at) = holes.dest {
let Some(Operand::Dest(name)) = operands.iter().find(|op| matches!(op, Operand::Dest(_)))
else {
return Err(format!("'{word}' left room for somewhere to go and was given nowhere"));
};
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: 0, 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 });
}
}
Ok(Written { bytes, holes: wanted })
}
fn spelled(
word: &str,
operands: &[Operand],
values: &[Value],
) -> Result<(String, &'static Encoding), String> {
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> {
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 value(operand: &Operand) -> Value {
match operand {
Operand::Reg(reg, width) => Value::Reg(*reg, *width),
Operand::High(reg) => Value::High(*reg),
Operand::Xmm(reg) => Value::Xmm(*reg),
Operand::Mem(addr, _) => Value::Mem(*addr),
Operand::Imm(number) => Value::Imm(*number),
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(Operand::Imm(number(rest.trim())?));
}
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(text.to_owned()));
}
Err(format!("'{text}' is not an operand this compiler reads"))
}
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)));
}
}
}
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();
let mut total: i64 = 0;
let mut sign: i64 = 1;
let mut start = 0usize;
let mut named: Option<String> = None;
let mut fold = |term: &str, sign: i64, named: &mut Option<String>| match number(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).map(|value| value as i64)
} else if digits.len() > 1 && digits.starts_with('0') {
i64::from_str_radix(&digits[1..], 8)
} else {
digits.parse::<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 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 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 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"));
}
#[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 an_instruction_this_compiler_has_no_bytes_for_is_refused_by_name() {
let why = refused("popcnt %rax, %rdx");
assert!(why.contains("popcnt"), "{why}");
}
}