mod encode;
mod insts;
mod text;
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::text::{Arg, Width, Written, gpr_name, written};
use crate::branch::{BranchInsts, Fusion};
use crate::frame::{ClassMoves, FrameInsts};
use crate::regs::{CallRegs, ClassInfo, PhysReg, RegClass, RegFile};
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",
align: "and_ri_64",
lea: "lea_64",
ret: "ret",
};
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",
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" },
];
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,
};
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,
};
#[cfg(test)]
mod tests {
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));
}
}