use super::rex::{self, LegacyPrefixes, OpcodeMap};
use crate::isa::x64::args::{Amode, Avx512TupleType};
use crate::isa::x64::inst::Inst;
use crate::MachBuffer;
use core::ops::RangeInclusive;
pub struct EvexInstruction {
bits: u32,
opcode: u8,
reg: Register,
rm: RegisterOrAmode,
tuple_type: Option<Avx512TupleType>,
imm: Option<u8>,
}
impl Default for EvexInstruction {
fn default() -> Self {
Self {
bits: 0x08_7C_F0_62,
opcode: 0,
reg: Register::default(),
rm: RegisterOrAmode::Register(Register::default()),
tuple_type: None,
imm: None,
}
}
}
#[allow(non_upper_case_globals)] impl EvexInstruction {
pub fn new() -> Self {
Self::default()
}
#[inline(always)]
pub fn length(mut self, length: EvexVectorLength) -> Self {
self.write(Self::LL, EvexContext::Other { length }.bits() as u32);
self
}
#[inline(always)]
pub fn prefix(mut self, prefix: LegacyPrefixes) -> Self {
self.write(Self::pp, prefix.bits() as u32);
self
}
#[inline(always)]
pub fn map(mut self, map: OpcodeMap) -> Self {
self.write(Self::mm, map.bits() as u32);
self
}
#[inline(always)]
pub fn w(mut self, w: bool) -> Self {
self.write(Self::W, w as u32);
self
}
#[inline(always)]
pub fn opcode(mut self, opcode: u8) -> Self {
self.opcode = opcode;
self
}
#[inline(always)]
pub fn tuple_type(mut self, tt: Avx512TupleType) -> Self {
self.tuple_type = Some(tt);
self
}
#[inline(always)]
pub fn reg(mut self, reg: impl Into<Register>) -> Self {
self.reg = reg.into();
let r = !(self.reg.0 >> 3) & 1;
let r_ = !(self.reg.0 >> 4) & 1;
self.write(Self::R, r as u32);
self.write(Self::R_, r_ as u32);
self
}
#[allow(dead_code)]
#[inline(always)]
pub fn mask(mut self, mask: EvexMasking) -> Self {
self.write(Self::aaa, mask.aaa_bits() as u32);
self.write(Self::z, mask.z_bit() as u32);
self
}
#[allow(dead_code)]
#[inline(always)]
pub fn vvvvv(mut self, reg: impl Into<Register>) -> Self {
let reg = reg.into();
self.write(Self::vvvv, !(reg.0 as u32) & 0b1111);
self.write(Self::V_, !(reg.0 as u32 >> 4) & 0b1);
self
}
#[inline(always)]
pub fn rm(mut self, reg: impl Into<RegisterOrAmode>) -> Self {
self.rm = reg.into();
let x = match &self.rm {
RegisterOrAmode::Register(r) => r.0 >> 4,
RegisterOrAmode::Amode(Amode::ImmRegRegShift { index, .. }) => {
index.to_real_reg().unwrap().hw_enc() >> 3
}
RegisterOrAmode::Amode(Amode::ImmReg { .. }) => 0,
RegisterOrAmode::Amode(Amode::RipRelative { .. }) => 0,
};
self.write(Self::X, u32::from(!x & 1));
let b = match &self.rm {
RegisterOrAmode::Register(r) => r.0 >> 3,
RegisterOrAmode::Amode(Amode::ImmReg { base, .. }) => {
base.to_real_reg().unwrap().hw_enc() >> 3
}
RegisterOrAmode::Amode(Amode::ImmRegRegShift { base, .. }) => {
base.to_real_reg().unwrap().hw_enc() >> 3
}
RegisterOrAmode::Amode(Amode::RipRelative { .. }) => 0,
};
self.write(Self::B, u32::from(!b & 1));
self
}
#[inline(always)]
pub fn imm(mut self, imm: u8) -> Self {
self.imm = Some(imm);
self
}
pub fn encode(&self, sink: &mut MachBuffer<Inst>) {
if let RegisterOrAmode::Amode(amode) = &self.rm {
if let Some(trap_code) = amode.get_flags().trap_code() {
sink.add_trap(trap_code);
}
}
sink.put4(self.bits);
sink.put1(self.opcode);
match &self.rm {
RegisterOrAmode::Register(reg) => {
let rm: u8 = (*reg).into();
sink.put1(rex::encode_modrm(3, self.reg.0 & 7, rm & 7));
}
RegisterOrAmode::Amode(amode) => {
let scaling = self.scaling_for_8bit_disp();
let bytes_at_end = if self.imm.is_some() { 1 } else { 0 };
rex::emit_modrm_sib_disp(sink, self.reg.0 & 7, amode, bytes_at_end, Some(scaling));
}
}
if let Some(imm) = self.imm {
sink.put1(imm);
}
}
const mm: RangeInclusive<u8> = 8..=9;
const R_: RangeInclusive<u8> = 12..=12;
const B: RangeInclusive<u8> = 13..=13;
const X: RangeInclusive<u8> = 14..=14;
const R: RangeInclusive<u8> = 15..=15;
const pp: RangeInclusive<u8> = 16..=17;
const vvvv: RangeInclusive<u8> = 19..=22;
const W: RangeInclusive<u8> = 23..=23;
const aaa: RangeInclusive<u8> = 24..=26;
const V_: RangeInclusive<u8> = 27..=27;
const b: RangeInclusive<u8> = 28..=28;
const LL: RangeInclusive<u8> = 29..=30;
const z: RangeInclusive<u8> = 31..=31;
#[inline]
fn write(&mut self, range: RangeInclusive<u8>, value: u32) {
assert!(ExactSizeIterator::len(&range) > 0);
let size = range.end() - range.start() + 1; let mask: u32 = (1 << size) - 1; debug_assert!(
value <= mask,
"The written value should have fewer than {size} bits."
);
let mask_complement = !(mask << *range.start()); self.bits &= mask_complement; let value = value << *range.start(); self.bits |= value; }
#[inline]
fn read(&self, range: RangeInclusive<u8>) -> u32 {
(self.bits >> range.start()) & ((1 << range.len()) - 1)
}
fn scaling_for_8bit_disp(&self) -> i8 {
use Avx512TupleType::*;
let vector_size_scaling = || match self.read(Self::LL) {
0b00 => 16,
0b01 => 32,
0b10 => 64,
_ => unreachable!(),
};
match self.tuple_type {
Some(Full) => {
if self.read(Self::b) == 1 {
if self.read(Self::W) == 0 {
4
} else {
8
}
} else {
vector_size_scaling()
}
}
Some(FullMem) => vector_size_scaling(),
Some(Mem128) => 16,
None => panic!("tuple type was not set"),
}
}
}
#[derive(Debug, Copy, Clone, Default)]
pub struct Register(u8);
impl From<u8> for Register {
fn from(reg: u8) -> Self {
debug_assert!(reg < 16);
Self(reg)
}
}
impl Into<u8> for Register {
fn into(self) -> u8 {
self.0
}
}
#[allow(missing_docs)]
#[derive(Debug, Clone)]
pub enum RegisterOrAmode {
Register(Register),
Amode(Amode),
}
impl From<u8> for RegisterOrAmode {
fn from(reg: u8) -> Self {
RegisterOrAmode::Register(reg.into())
}
}
impl From<Amode> for RegisterOrAmode {
fn from(amode: Amode) -> Self {
RegisterOrAmode::Amode(amode)
}
}
#[allow(dead_code, missing_docs)] pub enum EvexContext {
RoundingRegToRegFP {
rc: EvexRoundingControl,
},
NoRoundingFP {
sae: bool,
length: EvexVectorLength,
},
MemoryOp {
broadcast: bool,
length: EvexVectorLength,
},
Other {
length: EvexVectorLength,
},
}
impl Default for EvexContext {
fn default() -> Self {
Self::Other {
length: EvexVectorLength::default(),
}
}
}
impl EvexContext {
pub fn bits(&self) -> u8 {
match self {
Self::RoundingRegToRegFP { rc } => 0b001 | rc.bits() << 1,
Self::NoRoundingFP { sae, length } => (*sae as u8) | length.bits() << 1,
Self::MemoryOp { broadcast, length } => (*broadcast as u8) | length.bits() << 1,
Self::Other { length } => length.bits() << 1,
}
}
}
#[allow(dead_code, missing_docs)] pub enum EvexVectorLength {
V128,
V256,
V512,
}
impl EvexVectorLength {
fn bits(&self) -> u8 {
match self {
Self::V128 => 0b00,
Self::V256 => 0b01,
Self::V512 => 0b10,
}
}
}
impl Default for EvexVectorLength {
fn default() -> Self {
Self::V128
}
}
#[allow(dead_code, missing_docs)] pub enum EvexRoundingControl {
RNE,
RD,
RU,
RZ,
}
impl EvexRoundingControl {
fn bits(&self) -> u8 {
match self {
Self::RNE => 0b00,
Self::RD => 0b01,
Self::RU => 0b10,
Self::RZ => 0b11,
}
}
}
#[allow(dead_code, missing_docs)] pub enum EvexMasking {
None,
Merging { k: u8 },
Zeroing { k: u8 },
}
impl Default for EvexMasking {
fn default() -> Self {
EvexMasking::None
}
}
impl EvexMasking {
pub fn z_bit(&self) -> u8 {
match self {
Self::None | Self::Merging { .. } => 0,
Self::Zeroing { .. } => 1,
}
}
pub fn aaa_bits(&self) -> u8 {
match self {
Self::None => 0b000,
Self::Merging { k } | Self::Zeroing { k } => {
debug_assert!(*k <= 7);
*k
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::MemFlags;
use crate::isa::x64::args::Gpr;
use crate::isa::x64::inst::regs;
use std::vec::Vec;
#[test]
fn vpabsq() {
let mut tmp = MachBuffer::<Inst>::new();
let tests: &[(crate::Reg, RegisterOrAmode, Vec<u8>)] = &[
(
regs::xmm0(),
regs::xmm1().to_real_reg().unwrap().hw_enc().into(),
vec![0x62, 0xf2, 0xfd, 0x08, 0x1f, 0xc1],
),
(
regs::xmm10(),
regs::xmm8().to_real_reg().unwrap().hw_enc().into(),
vec![0x62, 0x52, 0xfd, 0x08, 0x1f, 0xd0],
),
(
regs::xmm3(),
regs::xmm15().to_real_reg().unwrap().hw_enc().into(),
vec![0x62, 0xd2, 0xfd, 0x08, 0x1f, 0xdf],
),
(
regs::xmm12(),
Amode::ImmReg {
simm32: 0,
base: regs::rsi(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x72, 0xfd, 0x08, 0x1f, 0x26],
),
(
regs::xmm14(),
Amode::ImmReg {
simm32: 8,
base: regs::r15(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x52, 0xfd, 0x08, 0x1f, 0xb7, 0x08, 0x00, 0x00, 0x00],
),
(
regs::xmm14(),
Amode::ImmReg {
simm32: 16,
base: regs::r15(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x52, 0xfd, 0x08, 0x1f, 0x77, 0x01],
),
(
regs::xmm3(),
Amode::ImmReg {
simm32: 17,
base: regs::rax(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0xf2, 0xfd, 0x08, 0x1f, 0x98, 0x11, 0x00, 0x00, 0x00],
),
(
regs::xmm9(),
Amode::ImmRegRegShift {
simm32: 0,
base: Gpr::unwrap_new(regs::rbx()),
index: Gpr::unwrap_new(regs::rsi()),
shift: 3,
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x72, 0xfd, 0x08, 0x1f, 0x0c, 0xf3],
),
(
regs::xmm13(),
Amode::ImmRegRegShift {
simm32: 1,
base: Gpr::unwrap_new(regs::r11()),
index: Gpr::unwrap_new(regs::rdi()),
shift: 2,
flags: MemFlags::trusted(),
}
.into(),
vec![
0x62, 0x52, 0xfd, 0x08, 0x1f, 0xac, 0xbb, 0x01, 0x00, 0x00, 0x00,
],
),
(
regs::xmm5(),
Amode::ImmRegRegShift {
simm32: 128,
base: Gpr::unwrap_new(regs::rsp()),
index: Gpr::unwrap_new(regs::r10()),
shift: 1,
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0xb2, 0xfd, 0x08, 0x1f, 0x6c, 0x54, 0x08],
),
(
regs::xmm6(),
Amode::ImmRegRegShift {
simm32: 112,
base: Gpr::unwrap_new(regs::rbp()),
index: Gpr::unwrap_new(regs::r13()),
shift: 0,
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0xb2, 0xfd, 0x08, 0x1f, 0x74, 0x2d, 0x07],
),
(
regs::xmm7(),
Amode::ImmRegRegShift {
simm32: 0,
base: Gpr::unwrap_new(regs::rbp()),
index: Gpr::unwrap_new(regs::r13()),
shift: 0,
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0xb2, 0xfd, 0x08, 0x1f, 0x7c, 0x2d, 0x00],
),
(
regs::xmm8(),
Amode::ImmReg {
simm32: 2032,
base: regs::r12(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x52, 0xfd, 0x08, 0x1f, 0x44, 0x24, 0x7f],
),
(
regs::xmm9(),
Amode::ImmReg {
simm32: 2048,
base: regs::r13(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x52, 0xfd, 0x08, 0x1f, 0x8d, 0x00, 0x08, 0x00, 0x00],
),
(
regs::xmm10(),
Amode::ImmReg {
simm32: -16,
base: regs::r14(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x52, 0xfd, 0x08, 0x1f, 0x56, 0xff],
),
(
regs::xmm11(),
Amode::ImmReg {
simm32: -5,
base: regs::r15(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x52, 0xfd, 0x08, 0x1f, 0x9f, 0xfb, 0xff, 0xff, 0xff],
),
(
regs::xmm12(),
Amode::ImmReg {
simm32: -2048,
base: regs::rdx(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x72, 0xfd, 0x08, 0x1f, 0x62, 0x80],
),
(
regs::xmm13(),
Amode::ImmReg {
simm32: -2064,
base: regs::rsi(),
flags: MemFlags::trusted(),
}
.into(),
vec![0x62, 0x72, 0xfd, 0x08, 0x1f, 0xae, 0xf0, 0xf7, 0xff, 0xff],
),
(
regs::xmm14(),
Amode::RipRelative {
target: tmp.get_label(),
}
.into(),
vec![0x62, 0x72, 0xfd, 0x08, 0x1f, 0x35, 0xf6, 0xff, 0xff, 0xff],
),
];
for (dst, src, encoding) in tests {
let mut sink = MachBuffer::new();
let label = sink.get_label();
sink.bind_label(label, &mut Default::default());
EvexInstruction::new()
.prefix(LegacyPrefixes::_66)
.map(OpcodeMap::_0F38)
.w(true)
.opcode(0x1F)
.reg(dst.to_real_reg().unwrap().hw_enc())
.rm(src.clone())
.length(EvexVectorLength::V128)
.tuple_type(Avx512TupleType::Full)
.encode(&mut sink);
let bytes0 = sink
.finish(&Default::default(), &mut Default::default())
.data;
assert_eq!(
bytes0.as_slice(),
encoding.as_slice(),
"dst={dst:?} src={src:?}"
);
}
}
#[test]
fn default_emission() {
let mut sink = MachBuffer::new();
EvexInstruction::new().encode(&mut sink);
let bytes0 = sink
.finish(&Default::default(), &mut Default::default())
.data;
let mut sink = MachBuffer::new();
EvexInstruction::new()
.length(EvexVectorLength::V128)
.prefix(LegacyPrefixes::None)
.map(OpcodeMap::None)
.w(false)
.opcode(0x00)
.reg(regs::rax().to_real_reg().unwrap().hw_enc())
.rm(regs::rax().to_real_reg().unwrap().hw_enc())
.mask(EvexMasking::None)
.encode(&mut sink);
let bytes1 = sink
.finish(&Default::default(), &mut Default::default())
.data;
assert_eq!(bytes0, bytes1);
}
}