use crate::operand::{Constraint, OperandDesc};
use crate::x86_64::{GPR, RAX, RCX, RDX, XMM};
use Form::{
AluRi, AluRr, ArgVal, BrCond, Call, CmpSet, Convert, DivQuo, DivRem, Jcc, Jmp, Lea, Load,
LoadImm, LoadVec, Move, MoveVec, Pop, Push, Ret, RetVal, ShiftCl, ShiftRi, Store, StoreVec,
Test, UnaryR,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Form {
LoadImm,
AluRr,
AluRi,
UnaryR,
ShiftRi,
ShiftCl,
CmpSet,
Convert,
DivQuo,
DivRem,
Lea,
Load,
Store,
RetVal,
ArgVal,
BrCond,
Test,
Jcc,
Jmp,
Call,
Move,
Push,
Pop,
Ret,
MoveVec,
LoadVec,
StoreVec,
}
static TWO_ADDRESS_RR: [OperandDesc; 3] = [
OperandDesc::write(GPR).with(Constraint::Reuse(1)),
OperandDesc::read(GPR),
OperandDesc::read(GPR),
];
static TWO_ADDRESS_RI: [OperandDesc; 2] =
[OperandDesc::write(GPR).with(Constraint::Reuse(1)), OperandDesc::read(GPR)];
static SHIFT_CL: [OperandDesc; 3] = [
OperandDesc::write(GPR).with(Constraint::Reuse(1)),
OperandDesc::read(GPR),
OperandDesc::read(GPR).with(Constraint::Fixed(RCX)),
];
static LOAD_IMM: [OperandDesc; 1] = [OperandDesc::write(GPR)];
static ONE_TO_ONE: [OperandDesc; 2] = [OperandDesc::write(GPR), OperandDesc::read(GPR)];
static TWO_TO_ONE: [OperandDesc; 3] =
[OperandDesc::write(GPR), OperandDesc::read(GPR), OperandDesc::read(GPR)];
static DIV_QUO: [OperandDesc; 4] = [
OperandDesc::write(GPR).with(Constraint::Fixed(RAX)),
OperandDesc::write_early(GPR).with(Constraint::Fixed(RDX)),
OperandDesc::read(GPR).with(Constraint::Fixed(RAX)),
OperandDesc::read(GPR),
];
static DIV_REM: [OperandDesc; 4] = [
OperandDesc::write(GPR).with(Constraint::Fixed(RDX)),
OperandDesc::write_early(GPR).with(Constraint::Fixed(RAX)),
OperandDesc::read(GPR).with(Constraint::Fixed(RAX)),
OperandDesc::read(GPR),
];
static ADDRESS: [OperandDesc; 1] = [OperandDesc::write(GPR)];
static LOAD: [OperandDesc; 1] = [OperandDesc::write(GPR)];
static STORE: [OperandDesc; 1] = [OperandDesc::read(GPR)];
static RET_VAL: [OperandDesc; 1] = [OperandDesc::read(GPR).with(Constraint::Fixed(RAX))];
static ARG_VAL: [OperandDesc; 1] = [OperandDesc::write(GPR)];
static BR_COND: [OperandDesc; 1] = [OperandDesc::read(GPR)];
static CALL: [OperandDesc; 0] = [];
static TEST: [OperandDesc; 1] = [OperandDesc::read(GPR)];
static JUMP: [OperandDesc; 0] = [];
static PUSH: [OperandDesc; 1] = [OperandDesc::read(GPR)];
static POP: [OperandDesc; 1] = [OperandDesc::write(GPR)];
static LEAVE: [OperandDesc; 0] = [];
static VEC_TO_VEC: [OperandDesc; 2] = [OperandDesc::write(XMM), OperandDesc::read(XMM)];
static LOAD_VEC: [OperandDesc; 1] = [OperandDesc::write(XMM)];
static STORE_VEC: [OperandDesc; 1] = [OperandDesc::read(XMM)];
impl Form {
#[must_use]
pub fn operands(self) -> &'static [OperandDesc] {
match self {
LoadImm => &LOAD_IMM,
AluRr => &TWO_ADDRESS_RR,
AluRi | UnaryR | ShiftRi => &TWO_ADDRESS_RI,
ShiftCl => &SHIFT_CL,
CmpSet => &TWO_TO_ONE,
Convert => &ONE_TO_ONE,
DivQuo => &DIV_QUO,
DivRem => &DIV_REM,
Lea => &ADDRESS,
Load => &LOAD,
Store => &STORE,
RetVal => &RET_VAL,
ArgVal => &ARG_VAL,
BrCond => &BR_COND,
Call => &CALL,
Test => &TEST,
Jcc | Jmp => &JUMP,
Move => &ONE_TO_ONE,
Push => &PUSH,
Pop => &POP,
Ret => &LEAVE,
MoveVec => &VEC_TO_VEC,
LoadVec => &LOAD_VEC,
StoreVec => &STORE_VEC,
}
}
#[must_use]
pub fn takes_imm(self) -> bool {
matches!(self, LoadImm | AluRi | ShiftRi)
}
#[must_use]
pub fn takes_mem(self) -> bool {
matches!(self, Lea | Load | Store | LoadVec | StoreVec)
}
}
pub static INSTS: &[(&str, Form)] = &[
("mov_ri_8", LoadImm),
("mov_ri_16", LoadImm),
("mov_ri_32", LoadImm),
("mov_ri_64", LoadImm),
("add_rr_8", AluRr),
("add_rr_16", AluRr),
("add_rr_32", AluRr),
("add_rr_64", AluRr),
("sub_rr_8", AluRr),
("sub_rr_16", AluRr),
("sub_rr_32", AluRr),
("sub_rr_64", AluRr),
("and_rr_8", AluRr),
("and_rr_16", AluRr),
("and_rr_32", AluRr),
("and_rr_64", AluRr),
("or_rr_8", AluRr),
("or_rr_16", AluRr),
("or_rr_32", AluRr),
("or_rr_64", AluRr),
("xor_rr_8", AluRr),
("xor_rr_16", AluRr),
("xor_rr_32", AluRr),
("xor_rr_64", AluRr),
("imul_rr_8", AluRr),
("imul_rr_16", AluRr),
("imul_rr_32", AluRr),
("imul_rr_64", AluRr),
("add_ri_8", AluRi),
("add_ri_16", AluRi),
("add_ri_32", AluRi),
("add_ri_64", AluRi),
("sub_ri_8", AluRi),
("sub_ri_16", AluRi),
("sub_ri_32", AluRi),
("sub_ri_64", AluRi),
("and_ri_8", AluRi),
("and_ri_16", AluRi),
("and_ri_32", AluRi),
("and_ri_64", AluRi),
("or_ri_8", AluRi),
("or_ri_16", AluRi),
("or_ri_32", AluRi),
("or_ri_64", AluRi),
("xor_ri_8", AluRi),
("xor_ri_16", AluRi),
("xor_ri_32", AluRi),
("xor_ri_64", AluRi),
("imul_ri_8", AluRi),
("imul_ri_16", AluRi),
("imul_ri_32", AluRi),
("imul_ri_64", AluRi),
("neg_r_8", UnaryR),
("neg_r_16", UnaryR),
("neg_r_32", UnaryR),
("neg_r_64", UnaryR),
("not_r_8", UnaryR),
("not_r_16", UnaryR),
("not_r_32", UnaryR),
("not_r_64", UnaryR),
("idiv_quo_8", DivQuo),
("idiv_quo_16", DivQuo),
("idiv_quo_32", DivQuo),
("idiv_quo_64", DivQuo),
("idiv_rem_8", DivRem),
("idiv_rem_16", DivRem),
("idiv_rem_32", DivRem),
("idiv_rem_64", DivRem),
("div_quo_8", DivQuo),
("div_quo_16", DivQuo),
("div_quo_32", DivQuo),
("div_quo_64", DivQuo),
("div_rem_8", DivRem),
("div_rem_16", DivRem),
("div_rem_32", DivRem),
("div_rem_64", DivRem),
("shl_ri_8", ShiftRi),
("shl_ri_16", ShiftRi),
("shl_ri_32", ShiftRi),
("shl_ri_64", ShiftRi),
("shr_ri_8", ShiftRi),
("shr_ri_16", ShiftRi),
("shr_ri_32", ShiftRi),
("shr_ri_64", ShiftRi),
("sar_ri_8", ShiftRi),
("sar_ri_16", ShiftRi),
("sar_ri_32", ShiftRi),
("sar_ri_64", ShiftRi),
("shl_rcl_8", ShiftCl),
("shl_rcl_16", ShiftCl),
("shl_rcl_32", ShiftCl),
("shl_rcl_64", ShiftCl),
("shr_rcl_8", ShiftCl),
("shr_rcl_16", ShiftCl),
("shr_rcl_32", ShiftCl),
("shr_rcl_64", ShiftCl),
("sar_rcl_8", ShiftCl),
("sar_rcl_16", ShiftCl),
("sar_rcl_32", ShiftCl),
("sar_rcl_64", ShiftCl),
("cmp_set_e_8", CmpSet),
("cmp_set_e_16", CmpSet),
("cmp_set_e_32", CmpSet),
("cmp_set_e_64", CmpSet),
("cmp_set_ne_8", CmpSet),
("cmp_set_ne_16", CmpSet),
("cmp_set_ne_32", CmpSet),
("cmp_set_ne_64", CmpSet),
("cmp_set_l_8", CmpSet),
("cmp_set_l_16", CmpSet),
("cmp_set_l_32", CmpSet),
("cmp_set_l_64", CmpSet),
("cmp_set_le_8", CmpSet),
("cmp_set_le_16", CmpSet),
("cmp_set_le_32", CmpSet),
("cmp_set_le_64", CmpSet),
("cmp_set_g_8", CmpSet),
("cmp_set_g_16", CmpSet),
("cmp_set_g_32", CmpSet),
("cmp_set_g_64", CmpSet),
("cmp_set_ge_8", CmpSet),
("cmp_set_ge_16", CmpSet),
("cmp_set_ge_32", CmpSet),
("cmp_set_ge_64", CmpSet),
("cmp_set_b_8", CmpSet),
("cmp_set_b_16", CmpSet),
("cmp_set_b_32", CmpSet),
("cmp_set_b_64", CmpSet),
("cmp_set_be_8", CmpSet),
("cmp_set_be_16", CmpSet),
("cmp_set_be_32", CmpSet),
("cmp_set_be_64", CmpSet),
("cmp_set_a_8", CmpSet),
("cmp_set_a_16", CmpSet),
("cmp_set_a_32", CmpSet),
("cmp_set_a_64", CmpSet),
("cmp_set_ae_8", CmpSet),
("cmp_set_ae_16", CmpSet),
("cmp_set_ae_32", CmpSet),
("cmp_set_ae_64", CmpSet),
("movzx_8_16", Convert),
("movzx_8_32", Convert),
("movzx_8_64", Convert),
("movzx_16_32", Convert),
("movzx_16_64", Convert),
("mov_32_to_64", Convert),
("movsx_8_16", Convert),
("movsx_8_32", Convert),
("movsx_8_64", Convert),
("movsx_16_32", Convert),
("movsx_16_64", Convert),
("movsxd_32_64", Convert),
("low_8", Convert),
("low_16", Convert),
("low_32", Convert),
("lea_64", Lea),
("mov_rm_8", Load),
("mov_rm_16", Load),
("mov_rm_32", Load),
("mov_rm_64", Load),
("mov_mr_8", Store),
("mov_mr_16", Store),
("mov_mr_32", Store),
("mov_mr_64", Store),
("ret_val_8", RetVal),
("ret_val_16", RetVal),
("ret_val_32", RetVal),
("ret_val_64", RetVal),
("arg_val_8", ArgVal),
("arg_val_16", ArgVal),
("arg_val_32", ArgVal),
("arg_val_64", ArgVal),
("br_cond_8", BrCond),
("call", Call),
("test_rr_8", Test),
("jcc_e", Jcc),
("jcc_ne", Jcc),
("jmp", Jmp),
("mov_rr_64", Move),
("push_64", Push),
("pop_64", Pop),
("ret", Ret),
("movaps_rr", MoveVec),
("movaps_rm", LoadVec),
("movaps_mr", StoreVec),
];
#[must_use]
pub fn form(name: &str) -> Option<Form> {
INSTS.iter().find(|(known, _)| *known == name).map(|&(_, form)| form)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Address {
BaseIndexScale,
IndexScale,
Base,
BaseOffset,
}
pub static ADDRESSES: &[(&str, Address)] = &[
("amode_base_index_scale", Address::BaseIndexScale),
("amode_index_scale", Address::IndexScale),
("amode_base", Address::Base),
("amode_base_offset", Address::BaseOffset),
];
#[must_use]
pub fn address(name: &str) -> Option<Address> {
ADDRESSES.iter().find(|(known, _)| *known == name).map(|&(_, kind)| kind)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operand::Role;
use crate::x86_64::{FRAME, SYSV, WIN64};
#[test]
fn every_opcode_is_described_once() {
let mut names: Vec<&str> = INSTS.iter().map(|&(name, _)| name).collect();
let described = names.len();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), described, "an opcode is described twice");
assert_eq!(described, 185);
}
#[test]
fn a_shape_writes_before_it_reads() {
for &(name, form) in INSTS {
let operands = form.operands();
let defs = operands.iter().filter(|operand| operand.role.is_def()).count();
assert!(
operands[..defs].iter().all(|operand| operand.role.is_def()),
"{name} writes an operand after one it reads"
);
assert!(
defs > 0
|| matches!(
form,
Store | RetVal | BrCond | Call | Test | Jcc | Jmp | Push | Ret | StoreVec
),
"{name} writes nothing and does nothing"
);
}
}
#[test]
fn a_two_address_form_ties_its_destination_to_its_first_source() {
for form in [AluRr, AluRi, UnaryR, ShiftRi, ShiftCl] {
assert_eq!(form.operands()[0].constraint, Constraint::Reuse(1));
}
for form in [CmpSet, Convert, LoadImm, Lea] {
assert_eq!(form.operands()[0].constraint, Constraint::Reg);
}
}
#[test]
fn a_division_names_the_registers_the_machine_insists_on() {
let quo = DivQuo.operands();
assert_eq!(quo[0].constraint, Constraint::Fixed(RAX));
assert_eq!(quo[1].constraint, Constraint::Fixed(RDX));
assert_eq!(quo[1].role, Role::EarlyDef, "the divisor may not be where the rest goes");
assert_eq!(quo[2].constraint, Constraint::Fixed(RAX));
assert_eq!(quo[3].constraint, Constraint::Reg);
let rem = DivRem.operands();
assert_eq!(rem[0].constraint, Constraint::Fixed(RDX));
assert_eq!(rem[1].constraint, Constraint::Fixed(RAX));
}
#[test]
fn a_return_leaves_the_value_where_both_conventions_look_for_it() {
assert_eq!(RetVal.operands()[0].constraint, Constraint::Fixed(RAX));
assert_eq!(SYSV.int_returns.first(), Some(&RAX));
assert_eq!(WIN64.int_returns.first(), Some(&RAX));
assert_eq!(RetVal.operands().len(), 1);
assert!(!RetVal.takes_imm() && !RetVal.takes_mem());
}
#[test]
fn an_argument_names_no_register_because_its_position_is_what_says_which_one() {
assert_eq!(ArgVal.operands()[0].constraint, Constraint::Reg);
assert_eq!(ArgVal.operands()[0].role, Role::Def);
assert_eq!(ArgVal.operands().len(), 1);
assert!(!ArgVal.takes_imm() && !ArgVal.takes_mem());
}
#[test]
fn a_shift_by_a_register_wants_it_in_cl() {
assert_eq!(ShiftCl.operands()[2].constraint, Constraint::Fixed(RCX));
assert!(!ShiftCl.takes_imm());
assert!(ShiftRi.takes_imm());
}
#[test]
fn only_the_shapes_that_carry_one_carry_an_immediate_or_an_address() {
assert!(LoadImm.takes_imm() && AluRi.takes_imm() && ShiftRi.takes_imm());
assert!(!AluRr.takes_imm() && !CmpSet.takes_imm() && !DivQuo.takes_imm());
assert!(Lea.takes_mem());
assert!(!AluRr.takes_mem() && !LoadImm.takes_mem());
}
#[test]
fn an_address_constructor_is_not_an_instruction() {
assert_eq!(address("amode_base_index_scale"), Some(Address::BaseIndexScale));
assert_eq!(address("amode_base_offset"), Some(Address::BaseOffset));
assert_eq!(address("amode_base"), Some(Address::Base));
assert_eq!(address("lea_64"), None);
assert_eq!(form("amode_index_scale"), None);
}
#[test]
fn every_instruction_the_block_layout_writes_is_described_here() {
use crate::x86_64::BRANCH;
assert_eq!(BRANCH.prefix, FRAME.prefix, "one target, one prefix");
assert_eq!(form(BRANCH.cond), Some(BrCond));
assert_eq!(form(BRANCH.test), Some(Test));
assert_eq!(form(BRANCH.if_true), Some(Jcc));
assert_eq!(form(BRANCH.if_false), Some(Jcc));
assert_eq!(form(BRANCH.jump), Some(Jmp));
assert_ne!(BRANCH.if_true, BRANCH.if_false, "the two arms are not the same jump");
}
#[test]
fn every_instruction_a_frame_is_made_of_is_described_here() {
assert_eq!(form(FRAME.push), Some(Push));
assert_eq!(form(FRAME.pop), Some(Pop));
assert_eq!(form(FRAME.ret), Some(Ret));
assert_eq!(form(FRAME.add), Some(AluRi));
assert_eq!(form(FRAME.sub), Some(AluRi));
assert_eq!(form(FRAME.align), Some(AluRi));
assert_eq!(form(FRAME.lea), Some(Lea));
let gpr = FRAME.classes[GPR.number() as usize];
assert_eq!(form(gpr.mov), Some(Move));
assert_eq!(form(gpr.load), Some(Load));
assert_eq!(form(gpr.store), Some(Store));
let xmm = FRAME.classes[XMM.number() as usize];
assert_eq!(form(xmm.mov), Some(MoveVec));
assert_eq!(form(xmm.load), Some(LoadVec));
assert_eq!(form(xmm.store), Some(StoreVec));
assert_eq!(MoveVec.operands()[0].class, XMM);
assert_eq!(Move.operands()[0].class, GPR);
}
#[test]
fn an_opcode_is_found_by_the_name_the_machine_ir_holds() {
assert_eq!(form("add_rr_32"), Some(AluRr));
assert_eq!(form("shl_rcl_64"), Some(ShiftCl));
assert_eq!(form("lea_64"), Some(Lea));
assert_eq!(form("x64.add_rr_32"), None, "the prefix is not part of the opcode");
assert_eq!(form("add_rr_128"), None);
}
}