mod encode;
mod insts;
mod read;
mod text;
mod timing;
pub use crate::x86_64::encode::{
Addr, Encoding, Error, Fields, Fits, Holes, ImmSize, Kind, Size, Value, encode, encoding,
};
pub use crate::x86_64::insts::{ADDRESSES, Address, Form, INSTS, address, form};
pub use crate::x86_64::read::{At, Line, Piece, read};
pub use crate::x86_64::text::{
Arg, Shape, Width, Written, gpr_letter, gpr_name, gpr_named, machine, operand_width, written,
};
pub use crate::x86_64::timing::{LOAD, MODEL, TIMING};
use crate::bits::BitInsts;
use crate::branch::{BranchInsts, Fusion};
use crate::flags::{Compare, FlagInsts, Reader, Reads, Zeroing};
use crate::frame::{ClassMoves, FrameInsts, Probe};
use crate::machine::MachineInsts;
use crate::operand::OperandDesc;
use crate::regs::{CallRegs, ClassInfo, Guard, PhysReg, RegClass, RegFile, Segment, Trace};
pub const GPR: RegClass = RegClass::new(0);
pub const XMM: RegClass = RegClass::new(1);
pub const X87: RegClass = RegClass::new(2);
pub const RAX: PhysReg = PhysReg::new(0);
pub const RCX: PhysReg = PhysReg::new(1);
pub const RDX: PhysReg = PhysReg::new(2);
pub const RBX: PhysReg = PhysReg::new(3);
pub const RSP: PhysReg = PhysReg::new(4);
pub const RBP: PhysReg = PhysReg::new(5);
pub const RSI: PhysReg = PhysReg::new(6);
pub const RDI: PhysReg = PhysReg::new(7);
pub const R8: PhysReg = PhysReg::new(8);
pub const R9: PhysReg = PhysReg::new(9);
pub const R10: PhysReg = PhysReg::new(10);
pub const R11: PhysReg = PhysReg::new(11);
pub const R12: PhysReg = PhysReg::new(12);
pub const R13: PhysReg = PhysReg::new(13);
pub const R14: PhysReg = PhysReg::new(14);
pub const R15: PhysReg = PhysReg::new(15);
#[must_use]
pub const fn xmm(number: u8) -> PhysReg {
assert!(number < 16, "x86-64 has sixteen vector registers without an extension");
PhysReg::new(number)
}
#[must_use]
pub const fn st(number: u8) -> PhysReg {
assert!(number < 8, "the x87 stack is eight deep");
PhysReg::new(number)
}
static GPR_NAMES: [&str; 16] = [
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13",
"r14", "r15",
];
static XMM_NAMES: [&str; 16] = [
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10",
"xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
];
static X87_NAMES: [&str; 8] = ["st0", "st1", "st2", "st3", "st4", "st5", "st6", "st7"];
static CLASSES: [ClassInfo; 3] = [
ClassInfo { name: "gpr", bits: 64, regs: &GPR_NAMES, allocatable: true },
ClassInfo { name: "xmm", bits: 128, regs: &XMM_NAMES, allocatable: true },
ClassInfo { name: "x87", bits: 80, regs: &X87_NAMES, allocatable: false },
];
pub static REGS: RegFile = RegFile::new(&CLASSES);
static GPR_DWARF: [u16; 16] = [0, 2, 1, 3, 7, 6, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15];
static XMM_DWARF: [u16; 16] = [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32];
static X86_64_DWARF: [&[u16]; 2] = [&GPR_DWARF, &XMM_DWARF];
pub const DWARF_RETURN_ADDRESS: u16 = 16;
static X86_64_MOVES: [ClassMoves; 2] = [
ClassMoves { mov: "mov_rr_64", load: "mov_rm_64", store: "mov_mr_64" },
ClassMoves { mov: "movaps_rr", load: "movaps_rm", store: "movaps_mr" },
];
pub static FRAME: FrameInsts = FrameInsts {
prefix: "x64.",
classes: &X86_64_MOVES,
push: "push_64",
pop: "pop_64",
add: "add_ri_64",
sub: "sub_ri_64",
grow: "sub_rr_64",
align: "and_ri_64",
lea: "lea_64",
ret: "ret",
differ: "cmp_set_ne_64",
call: "call",
probe: Some(PROBE),
landing: Some("endbr64"),
pad: Some("nop"),
};
pub static PROBE: Probe = Probe { inst: "or_mi_8", interval: 4096 };
pub static BITS: BitInsts = BitInsts { prefix: "x64.", width: operand_width, copies_low };
pub static MACHINE: MachineInsts = MachineInsts {
prefix: "x64.",
operands: machine_operands,
takes_imm: machine_takes_imm,
takes_mem: machine_takes_mem,
touches_mem: machine_touches_mem,
calls: machine_calls,
scales: &[1, 2, 4, 8],
};
#[must_use]
fn machine_operands(name: &str) -> Option<&'static [OperandDesc]> {
form(name).map(Form::operands)
}
#[must_use]
fn machine_takes_imm(name: &str) -> bool {
form(name).is_some_and(Form::takes_imm)
}
#[must_use]
fn machine_takes_mem(name: &str) -> bool {
form(name).is_some_and(Form::takes_mem)
}
#[must_use]
fn machine_touches_mem(name: &str) -> bool {
form(name).is_some_and(Form::touches_mem)
}
#[must_use]
fn machine_calls(name: &str) -> bool {
form(name) == Some(Form::Call)
}
#[must_use]
fn copies_low(name: &str) -> bool {
form(name) == Some(Form::Convert)
}
pub static BRANCH: BranchInsts = BranchInsts {
prefix: "x64.",
cond: "br_cond_8",
test: "test_rr_8",
if_true: "jcc_ne",
if_false: "jcc_e",
jump: "jmp",
indirect: "jmp_reg",
fused: &FUSED,
};
static FUSED: [Fusion; 80] = [
Fusion { set: "cmp_set_e_8", cmp: "cmp_rr_8", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_e_16", cmp: "cmp_rr_16", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_e_32", cmp: "cmp_rr_32", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_e_64", cmp: "cmp_rr_64", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_ne_8", cmp: "cmp_rr_8", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_ne_16", cmp: "cmp_rr_16", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_ne_32", cmp: "cmp_rr_32", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_ne_64", cmp: "cmp_rr_64", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_l_8", cmp: "cmp_rr_8", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_l_16", cmp: "cmp_rr_16", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_l_32", cmp: "cmp_rr_32", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_l_64", cmp: "cmp_rr_64", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_le_8", cmp: "cmp_rr_8", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_le_16", cmp: "cmp_rr_16", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_le_32", cmp: "cmp_rr_32", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_le_64", cmp: "cmp_rr_64", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_g_8", cmp: "cmp_rr_8", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_g_16", cmp: "cmp_rr_16", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_g_32", cmp: "cmp_rr_32", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_g_64", cmp: "cmp_rr_64", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_ge_8", cmp: "cmp_rr_8", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_ge_16", cmp: "cmp_rr_16", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_ge_32", cmp: "cmp_rr_32", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_ge_64", cmp: "cmp_rr_64", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_b_8", cmp: "cmp_rr_8", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_b_16", cmp: "cmp_rr_16", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_b_32", cmp: "cmp_rr_32", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_b_64", cmp: "cmp_rr_64", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_be_8", cmp: "cmp_rr_8", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_be_16", cmp: "cmp_rr_16", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_be_32", cmp: "cmp_rr_32", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_be_64", cmp: "cmp_rr_64", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_a_8", cmp: "cmp_rr_8", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_a_16", cmp: "cmp_rr_16", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_a_32", cmp: "cmp_rr_32", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_a_64", cmp: "cmp_rr_64", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_ae_8", cmp: "cmp_rr_8", if_true: "jcc_ae", if_false: "jcc_b" },
Fusion { set: "cmp_set_ae_16", cmp: "cmp_rr_16", if_true: "jcc_ae", if_false: "jcc_b" },
Fusion { set: "cmp_set_ae_32", cmp: "cmp_rr_32", if_true: "jcc_ae", if_false: "jcc_b" },
Fusion { set: "cmp_set_ae_64", cmp: "cmp_rr_64", if_true: "jcc_ae", if_false: "jcc_b" },
Fusion { set: "cmp_set_e_ri_8", cmp: "cmp_ri_8", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_e_ri_16", cmp: "cmp_ri_16", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_e_ri_32", cmp: "cmp_ri_32", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_e_ri_64", cmp: "cmp_ri_64", if_true: "jcc_e", if_false: "jcc_ne" },
Fusion { set: "cmp_set_ne_ri_8", cmp: "cmp_ri_8", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_ne_ri_16", cmp: "cmp_ri_16", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_ne_ri_32", cmp: "cmp_ri_32", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_ne_ri_64", cmp: "cmp_ri_64", if_true: "jcc_ne", if_false: "jcc_e" },
Fusion { set: "cmp_set_l_ri_8", cmp: "cmp_ri_8", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_l_ri_16", cmp: "cmp_ri_16", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_l_ri_32", cmp: "cmp_ri_32", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_l_ri_64", cmp: "cmp_ri_64", if_true: "jcc_l", if_false: "jcc_ge" },
Fusion { set: "cmp_set_le_ri_8", cmp: "cmp_ri_8", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_le_ri_16", cmp: "cmp_ri_16", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_le_ri_32", cmp: "cmp_ri_32", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_le_ri_64", cmp: "cmp_ri_64", if_true: "jcc_le", if_false: "jcc_g" },
Fusion { set: "cmp_set_g_ri_8", cmp: "cmp_ri_8", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_g_ri_16", cmp: "cmp_ri_16", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_g_ri_32", cmp: "cmp_ri_32", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_g_ri_64", cmp: "cmp_ri_64", if_true: "jcc_g", if_false: "jcc_le" },
Fusion { set: "cmp_set_ge_ri_8", cmp: "cmp_ri_8", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_ge_ri_16", cmp: "cmp_ri_16", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_ge_ri_32", cmp: "cmp_ri_32", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_ge_ri_64", cmp: "cmp_ri_64", if_true: "jcc_ge", if_false: "jcc_l" },
Fusion { set: "cmp_set_b_ri_8", cmp: "cmp_ri_8", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_b_ri_16", cmp: "cmp_ri_16", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_b_ri_32", cmp: "cmp_ri_32", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_b_ri_64", cmp: "cmp_ri_64", if_true: "jcc_b", if_false: "jcc_ae" },
Fusion { set: "cmp_set_be_ri_8", cmp: "cmp_ri_8", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_be_ri_16", cmp: "cmp_ri_16", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_be_ri_32", cmp: "cmp_ri_32", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_be_ri_64", cmp: "cmp_ri_64", if_true: "jcc_be", if_false: "jcc_a" },
Fusion { set: "cmp_set_a_ri_8", cmp: "cmp_ri_8", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_a_ri_16", cmp: "cmp_ri_16", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_a_ri_32", cmp: "cmp_ri_32", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_a_ri_64", cmp: "cmp_ri_64", if_true: "jcc_a", if_false: "jcc_be" },
Fusion { set: "cmp_set_ae_ri_8", cmp: "cmp_ri_8", if_true: "jcc_ae", if_false: "jcc_b" },
Fusion { set: "cmp_set_ae_ri_16", cmp: "cmp_ri_16", if_true: "jcc_ae", if_false: "jcc_b" },
Fusion { set: "cmp_set_ae_ri_32", cmp: "cmp_ri_32", if_true: "jcc_ae", if_false: "jcc_b" },
Fusion { set: "cmp_set_ae_ri_64", cmp: "cmp_ri_64", if_true: "jcc_ae", if_false: "jcc_b" },
];
pub static FLAGS: FlagInsts = FlagInsts {
prefix: "x64.",
width: operand_width,
writes: writes_flags,
compares: &COMPARES,
readers: &READERS,
zeroing: &ZEROING,
};
#[must_use]
fn writes_flags(name: &str) -> bool {
let Some(shape) = form(name) else { return true };
!matches!(
shape,
Form::Move
| Form::Convert
| Form::Lea
| Form::Load
| Form::Store
| Form::LoadImm
| Form::Push
| Form::Pop
| Form::Set
| Form::Cmov
| Form::Jcc
| Form::Jmp
| Form::Nop
| Form::Landing
| Form::Prefetch
| Form::RetVal
| Form::RetVal2
| Form::ArgVal
| Form::BrCond
| Form::MoveVec
| Form::LoadVec
| Form::StoreVec
| Form::AluVec
| Form::ConvertVec
| Form::ConvertToVec
| Form::ConvertFromVec
| Form::RetValVec
| Form::RetVal2Vec
| Form::ArgValVec
)
}
static COMPARES: [Compare; 88] = [
Compare { name: "cmp_set_e_8", asks: "cmp_rr_8", kept: Some("set_e") },
Compare { name: "cmp_set_e_16", asks: "cmp_rr_16", kept: Some("set_e") },
Compare { name: "cmp_set_e_32", asks: "cmp_rr_32", kept: Some("set_e") },
Compare { name: "cmp_set_e_64", asks: "cmp_rr_64", kept: Some("set_e") },
Compare { name: "cmp_set_ne_8", asks: "cmp_rr_8", kept: Some("set_ne") },
Compare { name: "cmp_set_ne_16", asks: "cmp_rr_16", kept: Some("set_ne") },
Compare { name: "cmp_set_ne_32", asks: "cmp_rr_32", kept: Some("set_ne") },
Compare { name: "cmp_set_ne_64", asks: "cmp_rr_64", kept: Some("set_ne") },
Compare { name: "cmp_set_l_8", asks: "cmp_rr_8", kept: Some("set_l") },
Compare { name: "cmp_set_l_16", asks: "cmp_rr_16", kept: Some("set_l") },
Compare { name: "cmp_set_l_32", asks: "cmp_rr_32", kept: Some("set_l") },
Compare { name: "cmp_set_l_64", asks: "cmp_rr_64", kept: Some("set_l") },
Compare { name: "cmp_set_le_8", asks: "cmp_rr_8", kept: Some("set_le") },
Compare { name: "cmp_set_le_16", asks: "cmp_rr_16", kept: Some("set_le") },
Compare { name: "cmp_set_le_32", asks: "cmp_rr_32", kept: Some("set_le") },
Compare { name: "cmp_set_le_64", asks: "cmp_rr_64", kept: Some("set_le") },
Compare { name: "cmp_set_g_8", asks: "cmp_rr_8", kept: Some("set_g") },
Compare { name: "cmp_set_g_16", asks: "cmp_rr_16", kept: Some("set_g") },
Compare { name: "cmp_set_g_32", asks: "cmp_rr_32", kept: Some("set_g") },
Compare { name: "cmp_set_g_64", asks: "cmp_rr_64", kept: Some("set_g") },
Compare { name: "cmp_set_ge_8", asks: "cmp_rr_8", kept: Some("set_ge") },
Compare { name: "cmp_set_ge_16", asks: "cmp_rr_16", kept: Some("set_ge") },
Compare { name: "cmp_set_ge_32", asks: "cmp_rr_32", kept: Some("set_ge") },
Compare { name: "cmp_set_ge_64", asks: "cmp_rr_64", kept: Some("set_ge") },
Compare { name: "cmp_set_b_8", asks: "cmp_rr_8", kept: Some("set_b") },
Compare { name: "cmp_set_b_16", asks: "cmp_rr_16", kept: Some("set_b") },
Compare { name: "cmp_set_b_32", asks: "cmp_rr_32", kept: Some("set_b") },
Compare { name: "cmp_set_b_64", asks: "cmp_rr_64", kept: Some("set_b") },
Compare { name: "cmp_set_be_8", asks: "cmp_rr_8", kept: Some("set_be") },
Compare { name: "cmp_set_be_16", asks: "cmp_rr_16", kept: Some("set_be") },
Compare { name: "cmp_set_be_32", asks: "cmp_rr_32", kept: Some("set_be") },
Compare { name: "cmp_set_be_64", asks: "cmp_rr_64", kept: Some("set_be") },
Compare { name: "cmp_set_a_8", asks: "cmp_rr_8", kept: Some("set_a") },
Compare { name: "cmp_set_a_16", asks: "cmp_rr_16", kept: Some("set_a") },
Compare { name: "cmp_set_a_32", asks: "cmp_rr_32", kept: Some("set_a") },
Compare { name: "cmp_set_a_64", asks: "cmp_rr_64", kept: Some("set_a") },
Compare { name: "cmp_set_ae_8", asks: "cmp_rr_8", kept: Some("set_ae") },
Compare { name: "cmp_set_ae_16", asks: "cmp_rr_16", kept: Some("set_ae") },
Compare { name: "cmp_set_ae_32", asks: "cmp_rr_32", kept: Some("set_ae") },
Compare { name: "cmp_set_ae_64", asks: "cmp_rr_64", kept: Some("set_ae") },
Compare { name: "cmp_set_e_ri_8", asks: "cmp_ri_8", kept: Some("set_e") },
Compare { name: "cmp_set_e_ri_16", asks: "cmp_ri_16", kept: Some("set_e") },
Compare { name: "cmp_set_e_ri_32", asks: "cmp_ri_32", kept: Some("set_e") },
Compare { name: "cmp_set_e_ri_64", asks: "cmp_ri_64", kept: Some("set_e") },
Compare { name: "cmp_set_ne_ri_8", asks: "cmp_ri_8", kept: Some("set_ne") },
Compare { name: "cmp_set_ne_ri_16", asks: "cmp_ri_16", kept: Some("set_ne") },
Compare { name: "cmp_set_ne_ri_32", asks: "cmp_ri_32", kept: Some("set_ne") },
Compare { name: "cmp_set_ne_ri_64", asks: "cmp_ri_64", kept: Some("set_ne") },
Compare { name: "cmp_set_l_ri_8", asks: "cmp_ri_8", kept: Some("set_l") },
Compare { name: "cmp_set_l_ri_16", asks: "cmp_ri_16", kept: Some("set_l") },
Compare { name: "cmp_set_l_ri_32", asks: "cmp_ri_32", kept: Some("set_l") },
Compare { name: "cmp_set_l_ri_64", asks: "cmp_ri_64", kept: Some("set_l") },
Compare { name: "cmp_set_le_ri_8", asks: "cmp_ri_8", kept: Some("set_le") },
Compare { name: "cmp_set_le_ri_16", asks: "cmp_ri_16", kept: Some("set_le") },
Compare { name: "cmp_set_le_ri_32", asks: "cmp_ri_32", kept: Some("set_le") },
Compare { name: "cmp_set_le_ri_64", asks: "cmp_ri_64", kept: Some("set_le") },
Compare { name: "cmp_set_g_ri_8", asks: "cmp_ri_8", kept: Some("set_g") },
Compare { name: "cmp_set_g_ri_16", asks: "cmp_ri_16", kept: Some("set_g") },
Compare { name: "cmp_set_g_ri_32", asks: "cmp_ri_32", kept: Some("set_g") },
Compare { name: "cmp_set_g_ri_64", asks: "cmp_ri_64", kept: Some("set_g") },
Compare { name: "cmp_set_ge_ri_8", asks: "cmp_ri_8", kept: Some("set_ge") },
Compare { name: "cmp_set_ge_ri_16", asks: "cmp_ri_16", kept: Some("set_ge") },
Compare { name: "cmp_set_ge_ri_32", asks: "cmp_ri_32", kept: Some("set_ge") },
Compare { name: "cmp_set_ge_ri_64", asks: "cmp_ri_64", kept: Some("set_ge") },
Compare { name: "cmp_set_b_ri_8", asks: "cmp_ri_8", kept: Some("set_b") },
Compare { name: "cmp_set_b_ri_16", asks: "cmp_ri_16", kept: Some("set_b") },
Compare { name: "cmp_set_b_ri_32", asks: "cmp_ri_32", kept: Some("set_b") },
Compare { name: "cmp_set_b_ri_64", asks: "cmp_ri_64", kept: Some("set_b") },
Compare { name: "cmp_set_be_ri_8", asks: "cmp_ri_8", kept: Some("set_be") },
Compare { name: "cmp_set_be_ri_16", asks: "cmp_ri_16", kept: Some("set_be") },
Compare { name: "cmp_set_be_ri_32", asks: "cmp_ri_32", kept: Some("set_be") },
Compare { name: "cmp_set_be_ri_64", asks: "cmp_ri_64", kept: Some("set_be") },
Compare { name: "cmp_set_a_ri_8", asks: "cmp_ri_8", kept: Some("set_a") },
Compare { name: "cmp_set_a_ri_16", asks: "cmp_ri_16", kept: Some("set_a") },
Compare { name: "cmp_set_a_ri_32", asks: "cmp_ri_32", kept: Some("set_a") },
Compare { name: "cmp_set_a_ri_64", asks: "cmp_ri_64", kept: Some("set_a") },
Compare { name: "cmp_set_ae_ri_8", asks: "cmp_ri_8", kept: Some("set_ae") },
Compare { name: "cmp_set_ae_ri_16", asks: "cmp_ri_16", kept: Some("set_ae") },
Compare { name: "cmp_set_ae_ri_32", asks: "cmp_ri_32", kept: Some("set_ae") },
Compare { name: "cmp_set_ae_ri_64", asks: "cmp_ri_64", kept: Some("set_ae") },
Compare { name: "cmp_rr_8", asks: "cmp_rr_8", kept: None },
Compare { name: "cmp_rr_16", asks: "cmp_rr_16", kept: None },
Compare { name: "cmp_rr_32", asks: "cmp_rr_32", kept: None },
Compare { name: "cmp_rr_64", asks: "cmp_rr_64", kept: None },
Compare { name: "cmp_ri_8", asks: "cmp_ri_8", kept: None },
Compare { name: "cmp_ri_16", asks: "cmp_ri_16", kept: None },
Compare { name: "cmp_ri_32", asks: "cmp_ri_32", kept: None },
Compare { name: "cmp_ri_64", asks: "cmp_ri_64", kept: None },
];
static READERS: [Reader; 130] = [
Reader { name: "cmp_set_e_8", reads: Reads::Zero },
Reader { name: "cmp_set_e_16", reads: Reads::Zero },
Reader { name: "cmp_set_e_32", reads: Reads::Zero },
Reader { name: "cmp_set_e_64", reads: Reads::Zero },
Reader { name: "cmp_set_ne_8", reads: Reads::Zero },
Reader { name: "cmp_set_ne_16", reads: Reads::Zero },
Reader { name: "cmp_set_ne_32", reads: Reads::Zero },
Reader { name: "cmp_set_ne_64", reads: Reads::Zero },
Reader { name: "cmp_set_l_8", reads: Reads::Signed },
Reader { name: "cmp_set_l_16", reads: Reads::Signed },
Reader { name: "cmp_set_l_32", reads: Reads::Signed },
Reader { name: "cmp_set_l_64", reads: Reads::Signed },
Reader { name: "cmp_set_le_8", reads: Reads::Signed },
Reader { name: "cmp_set_le_16", reads: Reads::Signed },
Reader { name: "cmp_set_le_32", reads: Reads::Signed },
Reader { name: "cmp_set_le_64", reads: Reads::Signed },
Reader { name: "cmp_set_g_8", reads: Reads::Signed },
Reader { name: "cmp_set_g_16", reads: Reads::Signed },
Reader { name: "cmp_set_g_32", reads: Reads::Signed },
Reader { name: "cmp_set_g_64", reads: Reads::Signed },
Reader { name: "cmp_set_ge_8", reads: Reads::Signed },
Reader { name: "cmp_set_ge_16", reads: Reads::Signed },
Reader { name: "cmp_set_ge_32", reads: Reads::Signed },
Reader { name: "cmp_set_ge_64", reads: Reads::Signed },
Reader { name: "cmp_set_b_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_b_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_b_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_b_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_e_ri_8", reads: Reads::Zero },
Reader { name: "cmp_set_e_ri_16", reads: Reads::Zero },
Reader { name: "cmp_set_e_ri_32", reads: Reads::Zero },
Reader { name: "cmp_set_e_ri_64", reads: Reads::Zero },
Reader { name: "cmp_set_ne_ri_8", reads: Reads::Zero },
Reader { name: "cmp_set_ne_ri_16", reads: Reads::Zero },
Reader { name: "cmp_set_ne_ri_32", reads: Reads::Zero },
Reader { name: "cmp_set_ne_ri_64", reads: Reads::Zero },
Reader { name: "cmp_set_l_ri_8", reads: Reads::Signed },
Reader { name: "cmp_set_l_ri_16", reads: Reads::Signed },
Reader { name: "cmp_set_l_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_l_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_le_ri_8", reads: Reads::Signed },
Reader { name: "cmp_set_le_ri_16", reads: Reads::Signed },
Reader { name: "cmp_set_le_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_le_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_g_ri_8", reads: Reads::Signed },
Reader { name: "cmp_set_g_ri_16", reads: Reads::Signed },
Reader { name: "cmp_set_g_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_g_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_ge_ri_8", reads: Reads::Signed },
Reader { name: "cmp_set_ge_ri_16", reads: Reads::Signed },
Reader { name: "cmp_set_ge_ri_32", reads: Reads::Signed },
Reader { name: "cmp_set_ge_ri_64", reads: Reads::Signed },
Reader { name: "cmp_set_b_ri_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_b_ri_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_b_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_b_ri_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_ri_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_ri_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_be_ri_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_ri_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_ri_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_a_ri_64", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_ri_8", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_ri_16", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_ri_32", reads: Reads::Unsigned },
Reader { name: "cmp_set_ae_ri_64", reads: Reads::Unsigned },
Reader { name: "set_e", reads: Reads::Zero },
Reader { name: "set_ne", reads: Reads::Zero },
Reader { name: "set_l", reads: Reads::Signed },
Reader { name: "set_le", reads: Reads::Signed },
Reader { name: "set_g", reads: Reads::Signed },
Reader { name: "set_ge", reads: Reads::Signed },
Reader { name: "set_b", reads: Reads::Unsigned },
Reader { name: "set_be", reads: Reads::Unsigned },
Reader { name: "set_a", reads: Reads::Unsigned },
Reader { name: "set_ae", reads: Reads::Unsigned },
Reader { name: "cmov_e_16", reads: Reads::Zero },
Reader { name: "cmov_e_32", reads: Reads::Zero },
Reader { name: "cmov_e_64", reads: Reads::Zero },
Reader { name: "cmov_ne_16", reads: Reads::Zero },
Reader { name: "cmov_ne_32", reads: Reads::Zero },
Reader { name: "cmov_ne_64", reads: Reads::Zero },
Reader { name: "cmov_l_16", reads: Reads::Signed },
Reader { name: "cmov_l_32", reads: Reads::Signed },
Reader { name: "cmov_l_64", reads: Reads::Signed },
Reader { name: "cmov_le_16", reads: Reads::Signed },
Reader { name: "cmov_le_32", reads: Reads::Signed },
Reader { name: "cmov_le_64", reads: Reads::Signed },
Reader { name: "cmov_g_16", reads: Reads::Signed },
Reader { name: "cmov_g_32", reads: Reads::Signed },
Reader { name: "cmov_g_64", reads: Reads::Signed },
Reader { name: "cmov_ge_16", reads: Reads::Signed },
Reader { name: "cmov_ge_32", reads: Reads::Signed },
Reader { name: "cmov_ge_64", reads: Reads::Signed },
Reader { name: "cmov_b_16", reads: Reads::Unsigned },
Reader { name: "cmov_b_32", reads: Reads::Unsigned },
Reader { name: "cmov_b_64", reads: Reads::Unsigned },
Reader { name: "cmov_be_16", reads: Reads::Unsigned },
Reader { name: "cmov_be_32", reads: Reads::Unsigned },
Reader { name: "cmov_be_64", reads: Reads::Unsigned },
Reader { name: "cmov_a_16", reads: Reads::Unsigned },
Reader { name: "cmov_a_32", reads: Reads::Unsigned },
Reader { name: "cmov_a_64", reads: Reads::Unsigned },
Reader { name: "cmov_ae_16", reads: Reads::Unsigned },
Reader { name: "cmov_ae_32", reads: Reads::Unsigned },
Reader { name: "cmov_ae_64", reads: Reads::Unsigned },
Reader { name: "jcc_e", reads: Reads::Zero },
Reader { name: "jcc_ne", reads: Reads::Zero },
Reader { name: "jcc_l", reads: Reads::Signed },
Reader { name: "jcc_le", reads: Reads::Signed },
Reader { name: "jcc_g", reads: Reads::Signed },
Reader { name: "jcc_ge", reads: Reads::Signed },
Reader { name: "jcc_b", reads: Reads::Unsigned },
Reader { name: "jcc_be", reads: Reads::Unsigned },
Reader { name: "jcc_a", reads: Reads::Unsigned },
Reader { name: "jcc_ae", reads: Reads::Unsigned },
];
static ZEROING: [Zeroing; 44] = [
Zeroing { name: "and_rr_8", signed: true, unsigned: true },
Zeroing { name: "and_rr_16", signed: true, unsigned: true },
Zeroing { name: "and_rr_32", signed: true, unsigned: true },
Zeroing { name: "and_rr_64", signed: true, unsigned: true },
Zeroing { name: "and_ri_8", signed: true, unsigned: true },
Zeroing { name: "and_ri_16", signed: true, unsigned: true },
Zeroing { name: "and_ri_32", signed: true, unsigned: true },
Zeroing { name: "and_ri_64", signed: true, unsigned: true },
Zeroing { name: "or_rr_8", signed: true, unsigned: true },
Zeroing { name: "or_rr_16", signed: true, unsigned: true },
Zeroing { name: "or_rr_32", signed: true, unsigned: true },
Zeroing { name: "or_rr_64", signed: true, unsigned: true },
Zeroing { name: "or_ri_8", signed: true, unsigned: true },
Zeroing { name: "or_ri_16", signed: true, unsigned: true },
Zeroing { name: "or_ri_32", signed: true, unsigned: true },
Zeroing { name: "or_ri_64", signed: true, unsigned: true },
Zeroing { name: "xor_rr_8", signed: true, unsigned: true },
Zeroing { name: "xor_rr_16", signed: true, unsigned: true },
Zeroing { name: "xor_rr_32", signed: true, unsigned: true },
Zeroing { name: "xor_rr_64", signed: true, unsigned: true },
Zeroing { name: "xor_ri_8", signed: true, unsigned: true },
Zeroing { name: "xor_ri_16", signed: true, unsigned: true },
Zeroing { name: "xor_ri_32", signed: true, unsigned: true },
Zeroing { name: "xor_ri_64", signed: true, unsigned: true },
Zeroing { name: "add_rr_8", signed: false, unsigned: false },
Zeroing { name: "add_rr_16", signed: false, unsigned: false },
Zeroing { name: "add_rr_32", signed: false, unsigned: false },
Zeroing { name: "add_rr_64", signed: false, unsigned: false },
Zeroing { name: "add_ri_8", signed: false, unsigned: false },
Zeroing { name: "add_ri_16", signed: false, unsigned: false },
Zeroing { name: "add_ri_32", signed: false, unsigned: false },
Zeroing { name: "add_ri_64", signed: false, unsigned: false },
Zeroing { name: "sub_rr_8", signed: false, unsigned: false },
Zeroing { name: "sub_rr_16", signed: false, unsigned: false },
Zeroing { name: "sub_rr_32", signed: false, unsigned: false },
Zeroing { name: "sub_rr_64", signed: false, unsigned: false },
Zeroing { name: "sub_ri_8", signed: false, unsigned: false },
Zeroing { name: "sub_ri_16", signed: false, unsigned: false },
Zeroing { name: "sub_ri_32", signed: false, unsigned: false },
Zeroing { name: "sub_ri_64", signed: false, unsigned: false },
Zeroing { name: "neg_r_8", signed: false, unsigned: false },
Zeroing { name: "neg_r_16", signed: false, unsigned: false },
Zeroing { name: "neg_r_32", signed: false, unsigned: false },
Zeroing { name: "neg_r_64", signed: false, unsigned: false },
];
static SYSV_INT_ARGS: [PhysReg; 6] = [RDI, RSI, RDX, RCX, R8, R9];
static SYSV_SSE_ARGS: [PhysReg; 8] =
[xmm(0), xmm(1), xmm(2), xmm(3), xmm(4), xmm(5), xmm(6), xmm(7)];
static SYSV_INT_RETURNS: [PhysReg; 2] = [RAX, RDX];
static SYSV_SSE_RETURNS: [PhysReg; 2] = [xmm(0), xmm(1)];
static SYSV_X87_RETURNS: [PhysReg; 2] = [st(0), st(1)];
static SYSV_INT_SAVED: [PhysReg; 6] = [RBX, RBP, R12, R13, R14, R15];
static SYSV_SSE_SAVED: [PhysReg; 0] = [];
static SYSV_INT_ORDER: [PhysReg; 14] =
[RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11, RBX, R12, R13, R14, R15];
static SSE_ORDER: [PhysReg; 16] = [
xmm(0),
xmm(1),
xmm(2),
xmm(3),
xmm(4),
xmm(5),
xmm(6),
xmm(7),
xmm(8),
xmm(9),
xmm(10),
xmm(11),
xmm(12),
xmm(13),
xmm(14),
xmm(15),
];
pub static SYSV: CallRegs = CallRegs {
int_class: GPR,
sse_class: XMM,
int_args: &SYSV_INT_ARGS,
sse_args: &SYSV_SSE_ARGS,
shared_positions: false,
int_returns: &SYSV_INT_RETURNS,
sse_returns: &SYSV_SSE_RETURNS,
x87_returns: &SYSV_X87_RETURNS,
int_saved: &SYSV_INT_SAVED,
sse_saved: &SYSV_SSE_SAVED,
int_order: &SYSV_INT_ORDER,
sse_order: &SSE_ORDER,
stack_pointer: RSP,
frame_pointer: RBP,
vector_count: Some(RAX),
red_zone: 128,
shadow: 0,
stack_align: 16,
return_address: 8,
word: 8,
dwarf: &X86_64_DWARF,
dwarf_return_address: DWARF_RETURN_ADDRESS,
guard: Some(Guard { segment: Segment::Fs, at: 40, fail: "__stack_chk_fail" }),
trace: Some(Trace { early: "__fentry__", late: "mcount", fentry: true }),
};
static WIN64_INT_ARGS: [PhysReg; 4] = [RCX, RDX, R8, R9];
static WIN64_SSE_ARGS: [PhysReg; 4] = [xmm(0), xmm(1), xmm(2), xmm(3)];
static WIN64_INT_RETURNS: [PhysReg; 1] = [RAX];
static WIN64_SSE_RETURNS: [PhysReg; 1] = [xmm(0)];
static WIN64_X87_RETURNS: [PhysReg; 0] = [];
static WIN64_INT_SAVED: [PhysReg; 8] = [RBX, RBP, RSI, RDI, R12, R13, R14, R15];
static WIN64_SSE_SAVED: [PhysReg; 10] =
[xmm(6), xmm(7), xmm(8), xmm(9), xmm(10), xmm(11), xmm(12), xmm(13), xmm(14), xmm(15)];
static WIN64_INT_ORDER: [PhysReg; 14] =
[RAX, RCX, RDX, R8, R9, R10, R11, RBX, RSI, RDI, R12, R13, R14, R15];
pub static WIN64: CallRegs = CallRegs {
int_class: GPR,
sse_class: XMM,
int_args: &WIN64_INT_ARGS,
sse_args: &WIN64_SSE_ARGS,
shared_positions: true,
int_returns: &WIN64_INT_RETURNS,
sse_returns: &WIN64_SSE_RETURNS,
x87_returns: &WIN64_X87_RETURNS,
int_saved: &WIN64_INT_SAVED,
sse_saved: &WIN64_SSE_SAVED,
int_order: &WIN64_INT_ORDER,
sse_order: &SSE_ORDER,
stack_pointer: RSP,
frame_pointer: RBP,
vector_count: None,
red_zone: 0,
shadow: 32,
stack_align: 16,
return_address: 8,
word: 8,
dwarf: &X86_64_DWARF,
dwarf_return_address: DWARF_RETURN_ADDRESS,
guard: None,
trace: None,
};
#[cfg(test)]
mod tests {
use crate::Role;
use super::*;
fn covers(order: &[PhysReg], count: usize) -> bool {
let mut seen: Vec<u8> = order.iter().map(|reg| reg.number()).collect();
seen.sort_unstable();
seen.dedup();
seen.len() == order.len() && order.len() == count
}
#[test]
fn the_file_numbers_registers_the_way_the_encoding_does() {
assert_eq!(REGS.name(GPR, RAX), Some("rax"));
assert_eq!(REGS.name(GPR, RSP), Some("rsp"));
assert_eq!(REGS.name(GPR, R15), Some("r15"));
assert_eq!(REGS.reg_named("rdi"), Some((GPR, RDI)));
assert_eq!(REGS.reg_named("xmm9"), Some((XMM, xmm(9))));
assert_eq!(REGS.reg_named("st0"), Some((X87, st(0))));
}
#[test]
fn dwarf_numbers_the_first_eight_registers_in_a_different_order() {
assert_eq!(SYSV.dwarf(GPR, RAX), Some(0));
assert_eq!(SYSV.dwarf(GPR, RDX), Some(1));
assert_eq!(SYSV.dwarf(GPR, RCX), Some(2));
assert_eq!(SYSV.dwarf(GPR, RBX), Some(3));
assert_eq!(SYSV.dwarf(GPR, RSI), Some(4));
assert_eq!(SYSV.dwarf(GPR, RDI), Some(5));
assert_eq!(SYSV.dwarf(GPR, RBP), Some(6));
assert_eq!(SYSV.dwarf(GPR, RSP), Some(7));
}
#[test]
fn dwarf_numbers_the_rest_the_way_the_machine_does() {
assert_eq!(SYSV.dwarf(GPR, R8), Some(8));
assert_eq!(SYSV.dwarf(GPR, R15), Some(15));
assert_eq!(SYSV.dwarf_return_address, 16);
assert_eq!(SYSV.dwarf(XMM, xmm(0)), Some(17));
assert_eq!(SYSV.dwarf(XMM, xmm(15)), Some(32));
assert_eq!(SYSV.dwarf(X87, st(0)), None);
assert_eq!(WIN64.dwarf(GPR, RCX), Some(2));
}
#[test]
fn the_file_gives_no_name_to_two_registers() {
assert_eq!(REGS.duplicate(), None);
assert_eq!(REGS.len(GPR), 16);
assert_eq!(REGS.len(XMM), 16);
assert_eq!(REGS.len(X87), 8);
}
#[test]
fn the_allocator_is_offered_every_register_but_the_two_the_frame_needs() {
for convention in [&SYSV, &WIN64] {
assert!(covers(convention.int_order, 14));
assert!(covers(convention.sse_order, 16));
assert!(!convention.int_order.contains(&RSP));
assert!(!convention.int_order.contains(&RBP));
}
}
#[test]
fn a_register_a_call_destroys_is_offered_before_one_it_preserves() {
for convention in [&SYSV, &WIN64] {
let first_saved = convention
.int_order
.iter()
.position(|®| convention.preserves_int(reg))
.expect("some register in the order is preserved");
assert!(
convention.int_order[..first_saved]
.iter()
.all(|®| !convention.preserves_int(reg)),
"the preserved registers are not one run at the end"
);
}
}
#[test]
fn the_two_conventions_disagree_where_the_psabis_do() {
assert_eq!(SYSV.int_args[0], RDI);
assert_eq!(WIN64.int_args[0], RCX);
assert!(!SYSV.preserves_int(RDI));
assert!(WIN64.preserves_int(RDI));
assert!(!SYSV.preserves_sse(xmm(6)));
assert!(WIN64.preserves_sse(xmm(6)));
assert_eq!((SYSV.red_zone, SYSV.shadow), (128, 0));
assert_eq!((WIN64.red_zone, WIN64.shadow), (0, 32));
assert_eq!(SYSV.vector_count, Some(RAX));
assert_eq!(WIN64.vector_count, None);
}
#[test]
fn a_long_double_comes_back_on_the_x87_stack_only_where_there_is_one() {
assert_eq!(SYSV.x87_returns, [st(0), st(1)]);
assert!(WIN64.x87_returns.is_empty());
}
#[test]
fn the_x87_stack_is_named_and_is_not_allocated_from() {
assert!(REGS.allocatable(GPR));
assert!(REGS.allocatable(XMM));
assert!(!REGS.allocatable(X87));
assert_eq!(REGS.name(X87, st(0)), Some("st0"));
}
#[test]
fn a_class_has_an_allocation_order_exactly_when_it_is_allocated_from() {
for convention in [&SYSV, &WIN64] {
let ordered = [
(convention.int_class, !convention.int_order.is_empty()),
(convention.sse_class, !convention.sse_order.is_empty()),
(X87, false),
];
for (class, has_order) in ordered {
assert_eq!(
REGS.allocatable(class),
has_order,
"{} says one thing and its allocation order says another",
REGS.class(class).expect("a class of this file").name
);
}
}
}
#[test]
fn every_comparison_is_one_the_layout_can_take_the_test_off() {
let described = |name: &str| INSTS.iter().any(|&(opcode, _)| opcode == name);
let sets: Vec<&str> = INSTS
.iter()
.filter(|&&(_, shape)| matches!(shape, Form::CmpSet | Form::CmpSetRi))
.map(|&(opcode, _)| opcode)
.collect();
let entries: Vec<&str> = BRANCH.fused.iter().map(|fusion| fusion.set).collect();
assert_eq!(entries, sets);
for fusion in BRANCH.fused {
assert!(
described(fusion.cmp),
"{} becomes {}, which is nothing",
fusion.set,
fusion.cmp
);
assert!(described(fusion.if_true), "{} is nothing", fusion.if_true);
assert!(described(fusion.if_false), "{} is nothing", fusion.if_false);
let before = form(fusion.set).expect("a described comparison").operands();
let after = form(fusion.cmp).expect("a described comparison").operands();
assert_eq!(after, &before[1..], "{} and {} disagree", fusion.set, fusion.cmp);
assert_eq!(form(fusion.if_true), Some(Form::Jcc));
assert_eq!(form(fusion.if_false), Some(Form::Jcc));
}
}
#[test]
fn the_two_jumps_a_comparison_becomes_are_a_condition_and_its_opposite() {
for fusion in BRANCH.fused {
let back = BRANCH
.fused
.iter()
.find(|other| other.if_true == fusion.if_false && other.cmp == fusion.cmp)
.unwrap_or_else(|| panic!("{} has no opposite", fusion.if_false));
assert_eq!(back.if_false, fusion.if_true, "{} is not an opposite", fusion.if_true);
}
let jumps: Vec<&str> = BRANCH.fused.iter().map(|fusion| fusion.if_true).collect();
assert!(jumps.contains(&BRANCH.if_true) && jumps.contains(&BRANCH.if_false));
}
fn condition(name: &str) -> &str {
let name = name
.strip_prefix("cmp_set_")
.or_else(|| name.strip_prefix("set_"))
.or_else(|| name.strip_prefix("cmov_"))
.or_else(|| name.strip_prefix("jcc_"))
.expect("an opcode with a condition in its name");
let name = name.rsplit_once('_').map_or(name, |(front, back)| {
if matches!(back, "8" | "16" | "32" | "64") { front } else { name }
});
name.strip_suffix("_ri").unwrap_or(name)
}
#[test]
fn every_comparison_has_an_entry_saying_what_it_asks_and_what_is_left_of_it() {
let described = |name: &str| INSTS.iter().any(|&(opcode, _)| opcode == name);
let comparisons: Vec<&str> = INSTS
.iter()
.filter(|&&(_, shape)| {
matches!(shape, Form::CmpSet | Form::CmpSetRi | Form::Cmp | Form::CmpRi)
})
.map(|&(opcode, _)| opcode)
.collect();
let entries: Vec<&str> = COMPARES.iter().map(|entry| entry.name).collect();
assert_eq!(entries, comparisons);
for entry in &COMPARES {
assert!(described(entry.asks), "{} asks {}, which is nothing", entry.name, entry.asks);
let operands = form(entry.name).expect("a described comparison").operands();
match entry.kept {
Some(kept) => {
assert!(described(kept), "{} becomes {}, which is nothing", entry.name, kept);
assert_eq!(form(kept), Some(Form::Set), "{kept} is not a byte on its own");
assert_eq!(form(kept).expect("a described byte").operands(), &operands[..1]);
let asks = form(entry.asks).expect("a described comparison").operands();
assert_eq!(asks, &operands[1..], "{} and {} disagree", entry.name, entry.asks);
}
None => assert_eq!(entry.asks, entry.name),
}
}
}
#[test]
fn every_condition_says_which_part_of_the_state_it_is_about() {
let readers: Vec<&str> = INSTS
.iter()
.filter(|&&(_, shape)| {
matches!(shape, Form::CmpSet | Form::CmpSetRi | Form::Set | Form::Cmov | Form::Jcc)
})
.map(|&(opcode, _)| opcode)
.collect();
let entries: Vec<&str> = READERS.iter().map(|entry| entry.name).collect();
assert_eq!(entries, readers);
for entry in &READERS {
let wanted = match condition(entry.name) {
"e" | "ne" => Reads::Zero,
"l" | "le" | "g" | "ge" => Reads::Signed,
"b" | "be" | "a" | "ae" => Reads::Unsigned,
other => panic!("{} asks about {other}, which is nothing", entry.name),
};
assert_eq!(entry.reads, wanted, "{} reads the wrong part", entry.name);
}
}
#[test]
fn a_byte_a_move_or_a_jump_leaves_the_condition_state_where_it_found_it() {
for entry in &READERS {
if matches!(form(entry.name), Some(Form::Set | Form::Cmov | Form::Jcc)) {
assert!(!writes_flags(entry.name), "{} is said to write the state", entry.name);
} else {
assert!(writes_flags(entry.name), "{} makes a comparison", entry.name);
}
}
}
#[test]
fn what_leaves_a_comparison_behind_is_arithmetic_with_one_answer() {
for entry in &ZEROING {
let shape = form(entry.name).expect("a described instruction");
assert!(
matches!(shape, Form::AluRr | Form::AluRi | Form::UnaryR),
"{} is not arithmetic",
entry.name
);
assert!(writes_flags(entry.name), "{} leaves the state alone", entry.name);
let written: Vec<u8> = shape
.operands()
.iter()
.enumerate()
.filter(|(_, operand)| matches!(operand.role, Role::Def | Role::EarlyDef))
.map(|(at, _)| u8::try_from(at).expect("an operand index"))
.collect();
assert_eq!(written.len(), 1, "{} does not write one register", entry.name);
assert!(
operand_width(entry.name, written[0]).is_some(),
"{} writes a register of no stated width",
entry.name
);
let name = entry.name;
assert!(
!name.starts_with("shl") && !name.starts_with("shr") && !name.starts_with("sar"),
"{name} is a shift, and a shift by zero leaves the state alone"
);
assert!(!name.starts_with("imul"), "{name} leaves the zero bit undefined");
assert!(
!name.starts_with("inc") && !name.starts_with("dec"),
"{name} leaves the carry alone"
);
}
}
}