use crate::operand::{Constraint, OperandDesc};
use crate::x86_64::{GPR, RAX, RCX, RDX};
use Form::{AluRi, AluRr, CmpSet, Convert, DivQuo, DivRem, Lea, LoadImm, ShiftCl, ShiftRi, UnaryR};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Form {
LoadImm,
AluRr,
AluRi,
UnaryR,
ShiftRi,
ShiftCl,
CmpSet,
Convert,
DivQuo,
DivRem,
Lea,
}
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)];
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,
}
}
#[must_use]
pub fn takes_imm(self) -> bool {
matches!(self, LoadImm | AluRi | ShiftRi)
}
#[must_use]
pub fn takes_mem(self) -> bool {
matches!(self, Lea)
}
}
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),
];
#[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,
}
impl Address {
#[must_use]
pub fn has_base(self) -> bool {
matches!(self, Address::BaseIndexScale)
}
}
pub static ADDRESSES: &[(&str, Address)] = &[
("amode_base_index_scale", Address::BaseIndexScale),
("amode_index_scale", Address::IndexScale),
];
#[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;
#[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, 156);
}
#[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!(defs > 0, "{name} writes nothing");
assert!(
operands[..defs].iter().all(|operand| operand.role.is_def()),
"{name} writes an operand after one it reads"
);
}
}
#[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_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!(Address::BaseIndexScale.has_base());
assert!(!Address::IndexScale.has_base());
assert_eq!(address("lea_64"), None);
assert_eq!(form("amode_index_scale"), None);
}
#[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);
}
}