use std::fmt;
use super::x86_vex_encoding::{
vex_invert_reg, vex_recover_reg, vex_reg_needs_b, vex_reg_needs_r, vex_reg_needs_x,
VexMandatoryPrefix, VexOpcodeMap, VexVectorLength,
};
pub const EVEX_PREFIX: u8 = 0x62;
pub const EVEX_MM_0F: u8 = 0x01;
pub const EVEX_MM_0F38: u8 = 0x02;
pub const EVEX_MM_0F3A: u8 = 0x03;
pub const EVEX_MM_5: u8 = 0x05;
pub const EVEX_MM_6: u8 = 0x06;
pub const EVEX_MM_7: u8 = 0x07;
pub const EVEX_LL_128: u8 = 0x00; pub const EVEX_LL_256: u8 = 0x01; pub const EVEX_LL_512: u8 = 0x02;
pub const EVEX_PP_NONE: u8 = 0x00;
pub const EVEX_PP_66: u8 = 0x01;
pub const EVEX_PP_F3: u8 = 0x02;
pub const EVEX_PP_F2: u8 = 0x03;
const EVEX_P1_FIXED_BIT: u8 = 0x04;
const EVEX_P0_R_PRIME_MASK: u8 = 0x80;
const EVEX_P0_X_PRIME_MASK: u8 = 0x40;
const EVEX_P0_B_PRIME_MASK: u8 = 0x20;
const EVEX_P0_R_MASK: u8 = 0x10;
const EVEX_P0_MM_MASK: u8 = 0x03;
const EVEX_P1_W_MASK: u8 = 0x80;
const EVEX_P1_VVVV_MASK: u8 = 0x78;
const EVEX_P1_PP_MASK: u8 = 0x03;
const EVEX_P2_Z_MASK: u8 = 0x80;
const EVEX_P2_L_PRIME_MASK: u8 = 0x40;
const EVEX_P2_L_MASK: u8 = 0x20;
const EVEX_P2_B_MASK: u8 = 0x10;
const EVEX_P2_V_PRIME_MASK: u8 = 0x08;
const EVEX_P2_AAA_MASK: u8 = 0x07;
pub const OPMASK_K0: u8 = 0;
pub const OPMASK_K1: u8 = 1;
pub const OPMASK_K2: u8 = 2;
pub const OPMASK_K3: u8 = 3;
pub const OPMASK_K4: u8 = 4;
pub const OPMASK_K5: u8 = 5;
pub const OPMASK_K6: u8 = 6;
pub const OPMASK_K7: u8 = 7;
pub const OPMASK_NONE: u8 = OPMASK_K0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EvexOpcodeMap {
Map0F = 0x01,
Map0F38 = 0x02,
Map0F3A = 0x03,
Map5 = 0x05,
Map6 = 0x06,
Map7 = 0x07,
}
impl EvexOpcodeMap {
pub const fn mm(&self) -> u8 {
*self as u8
}
pub const fn from_mm(mm: u8) -> Option<Self> {
match mm {
0x01 => Some(Self::Map0F),
0x02 => Some(Self::Map0F38),
0x03 => Some(Self::Map0F3A),
0x05 => Some(Self::Map5),
0x06 => Some(Self::Map6),
0x07 => Some(Self::Map7),
_ => None,
}
}
pub const fn name(&self) -> &'static str {
match self {
Self::Map0F => "0F",
Self::Map0F38 => "0F38",
Self::Map0F3A => "0F3A",
Self::Map5 => "MAP5",
Self::Map6 => "MAP6",
Self::Map7 => "MAP7",
}
}
pub const fn is_vex_equivalent(&self) -> bool {
matches!(self, Self::Map0F | Self::Map0F38 | Self::Map0F3A)
}
pub const fn to_vex_map(&self) -> Option<VexOpcodeMap> {
match self {
Self::Map0F => Some(VexOpcodeMap::Map0F),
Self::Map0F38 => Some(VexOpcodeMap::Map0F38),
Self::Map0F3A => Some(VexOpcodeMap::Map0F3A),
_ => None,
}
}
}
impl fmt::Display for EvexOpcodeMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EvexVectorLength {
VL128,
VL256,
VL512,
}
impl EvexVectorLength {
pub const fn ll_bits(&self) -> (bool, bool) {
match self {
Self::VL128 => (false, false),
Self::VL256 => (false, true),
Self::VL512 => (true, false),
}
}
pub const fn ll_value(&self) -> u8 {
match self {
Self::VL128 => 0,
Self::VL256 => 1,
Self::VL512 => 2,
}
}
pub const fn width_bytes(&self) -> u16 {
match self {
Self::VL128 => 16,
Self::VL256 => 32,
Self::VL512 => 64,
}
}
pub const fn width_bits(&self) -> u16 {
match self {
Self::VL128 => 128,
Self::VL256 => 256,
Self::VL512 => 512,
}
}
pub const fn reg_prefix(&self) -> &'static str {
match self {
Self::VL128 => "xmm",
Self::VL256 => "ymm",
Self::VL512 => "zmm",
}
}
pub const fn from_ll_bits(l_prime: bool, l: bool) -> Option<Self> {
match (l_prime, l) {
(false, false) => Some(Self::VL128),
(false, true) => Some(Self::VL256),
(true, false) => Some(Self::VL512),
(true, true) => None, }
}
pub const fn to_vex_vl(&self) -> VexVectorLength {
match self {
Self::VL128 => VexVectorLength::L128,
Self::VL256 => VexVectorLength::L256,
Self::VL512 => VexVectorLength::L256, }
}
}
impl fmt::Display for EvexVectorLength {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.width_bits())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EvexTupleType {
Full,
FullMem,
Half,
HalfMem,
Quarter,
QuarterMem,
Eighth,
EighthMem,
Scalar,
Mem128,
Tuple1Scalar,
Tuple2,
Tuple4,
Tuple8,
}
impl EvexTupleType {
pub fn multiplier(&self, vl: EvexVectorLength, element_size: u8) -> u8 {
let vl_bytes = vl.width_bytes();
match self {
Self::Full | Self::FullMem | Self::Tuple1Scalar => element_size,
Self::Half | Self::HalfMem | Self::Tuple2 => (vl_bytes as u32 / 2).min(255) as u8,
Self::Quarter | Self::QuarterMem | Self::Tuple4 => (vl_bytes as u32 / 4).min(255) as u8,
Self::Eighth | Self::EighthMem | Self::Tuple8 => (vl_bytes as u32 / 8).min(255) as u8,
Self::Scalar => element_size,
Self::Mem128 => 16,
}
}
pub const fn is_memory(&self) -> bool {
matches!(
self,
Self::FullMem | Self::HalfMem | Self::QuarterMem | Self::EighthMem
)
}
pub const fn is_memory_only(&self) -> bool {
self.is_memory()
}
pub const fn name(&self) -> &'static str {
match self {
Self::Full => "Full",
Self::FullMem => "FullMem",
Self::Half => "Half",
Self::HalfMem => "HalfMem",
Self::Quarter => "Quarter",
Self::QuarterMem => "QuarterMem",
Self::Eighth => "Eighth",
Self::EighthMem => "EighthMem",
Self::Scalar => "Scalar",
Self::Mem128 => "Mem128",
Self::Tuple1Scalar => "Tuple1Scalar",
Self::Tuple2 => "Tuple2",
Self::Tuple4 => "Tuple4",
Self::Tuple8 => "Tuple8",
}
}
}
impl fmt::Display for EvexTupleType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvexRoundingMode {
None,
RnSae,
RdSae,
RuSae,
RzSae,
Sae,
}
impl EvexRoundingMode {
pub const fn rc_bits(&self) -> u8 {
match self {
Self::RnSae => 0b00,
Self::RdSae => 0b01,
Self::RuSae => 0b10,
Self::RzSae => 0b11,
_ => 0,
}
}
pub const fn is_active(&self) -> bool {
!matches!(self, Self::None)
}
pub const fn has_sae(&self) -> bool {
matches!(
self,
Self::RnSae | Self::RdSae | Self::RuSae | Self::RzSae | Self::Sae
)
}
pub const fn has_explicit_rc(&self) -> bool {
matches!(self, Self::RnSae | Self::RdSae | Self::RuSae | Self::RzSae)
}
pub const fn suffix(&self) -> &'static str {
match self {
Self::None => "",
Self::RnSae => "{rn-sae}",
Self::RdSae => "{rd-sae}",
Self::RuSae => "{ru-sae}",
Self::RzSae => "{rz-sae}",
Self::Sae => "{sae}",
}
}
pub fn encode_ll_for_rounding(&self, vl: EvexVectorLength) -> (bool, bool) {
if self.is_active() {
match vl {
EvexVectorLength::VL128 => (false, false), EvexVectorLength::VL256 => (false, true), EvexVectorLength::VL512 => (true, false), }
} else {
vl.ll_bits()
}
}
}
impl fmt::Display for EvexRoundingMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.suffix())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvexBroadcastMode {
None,
Broadcast1To2,
Broadcast1To4,
Broadcast1To8,
Broadcast1To16,
Broadcast1To32,
}
impl EvexBroadcastMode {
pub const fn is_broadcast(&self) -> bool {
!matches!(self, Self::None)
}
pub const fn element_count(&self) -> u8 {
match self {
Self::None => 1,
Self::Broadcast1To2 => 2,
Self::Broadcast1To4 => 4,
Self::Broadcast1To8 => 8,
Self::Broadcast1To16 => 16,
Self::Broadcast1To32 => 32,
}
}
pub const fn to_b_bit(&self) -> bool {
self.is_broadcast()
}
pub fn broadcast_disp_multiplier(&self, element_size: u8) -> u8 {
element_size
}
pub const fn suffix(&self) -> &'static str {
match self {
Self::None => "",
Self::Broadcast1To2 => "{1to2}",
Self::Broadcast1To4 => "{1to4}",
Self::Broadcast1To8 => "{1to8}",
Self::Broadcast1To16 => "{1to16}",
Self::Broadcast1To32 => "{1to32}",
}
}
}
impl fmt::Display for EvexBroadcastMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.suffix())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EvexOpmaskConfig {
pub opmask_reg: u8,
pub zeroing: bool,
}
impl Default for EvexOpmaskConfig {
fn default() -> Self {
Self {
opmask_reg: OPMASK_NONE,
zeroing: false,
}
}
}
impl EvexOpmaskConfig {
pub fn new(opmask_reg: u8, zeroing: bool) -> Self {
Self {
opmask_reg,
zeroing,
}
}
pub fn none() -> Self {
Self::default()
}
pub fn merge(reg: u8) -> Self {
Self {
opmask_reg: reg & 0x07,
zeroing: false,
}
}
pub fn zero(reg: u8) -> Self {
Self {
opmask_reg: reg & 0x07,
zeroing: true,
}
}
pub fn has_active_mask(&self) -> bool {
self.opmask_reg != OPMASK_NONE
}
pub fn has_zeroing(&self) -> bool {
self.zeroing && self.has_active_mask()
}
pub fn has_merging(&self) -> bool {
self.has_active_mask() && !self.zeroing
}
pub fn suffix(&self) -> String {
if !self.has_active_mask() {
return String::new();
}
let mut s = format!("{{k{}}}", self.opmask_reg);
if self.zeroing {
s.push_str("{z}");
}
s
}
}
impl fmt::Display for EvexOpmaskConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.suffix())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EvexPrefixConfig {
pub r_prime: bool,
pub x_prime: bool,
pub b_prime: bool,
pub r: bool,
pub x: bool,
pub b: bool,
pub mm: u8,
pub v_prime: bool,
pub w: bool,
pub vvvv: u8,
pub pp: u8,
pub z: bool,
pub l_prime: bool,
pub l: bool,
pub b_bit: bool,
pub aaa: u8,
}
impl Default for EvexPrefixConfig {
fn default() -> Self {
Self {
r_prime: false,
x_prime: false,
b_prime: false,
r: false,
x: false,
b: false,
mm: EVEX_MM_0F,
v_prime: false,
w: false,
vvvv: 0,
pp: EVEX_PP_NONE,
z: false,
l_prime: false,
l: false,
b_bit: false,
aaa: OPMASK_NONE,
}
}
}
impl EvexPrefixConfig {
pub const fn new() -> Self {
Self {
r_prime: false,
x_prime: false,
b_prime: false,
r: false,
x: false,
b: false,
mm: EVEX_MM_0F,
v_prime: false,
w: false,
vvvv: 0,
pp: EVEX_PP_NONE,
z: false,
l_prime: false,
l: false,
b_bit: false,
aaa: OPMASK_NONE,
}
}
pub fn with_map(mut self, map: EvexOpcodeMap) -> Self {
self.mm = map.mm();
self
}
pub fn with_prefix(mut self, pfx: VexMandatoryPrefix) -> Self {
self.pp = pfx.pp_bits();
self
}
pub fn with_vector_length(mut self, vl: EvexVectorLength) -> Self {
let (lp, l) = vl.ll_bits();
self.l_prime = lp;
self.l = l;
self
}
pub fn with_ll(mut self, ll: u8) -> Self {
self.l_prime = (ll & 0x02) != 0;
self.l = (ll & 0x01) != 0;
self
}
pub fn with_vvvv(mut self, reg: u8) -> Self {
self.vvvv = vex_invert_reg(reg & 0x0F);
self.v_prime = vex_reg_needs_r(reg & 0x1F);
self
}
pub fn with_dest_reg(mut self, reg: u8) -> Self {
self.r = vex_reg_needs_r(reg);
self.r_prime = (reg & 0x10) != 0;
self
}
pub fn with_base_reg(mut self, reg: u8) -> Self {
self.b = vex_reg_needs_b(reg);
self.b_prime = (reg & 0x10) != 0;
self
}
pub fn with_index_reg(mut self, reg: u8) -> Self {
self.x = vex_reg_needs_x(reg);
self.x_prime = (reg & 0x10) != 0;
self
}
pub fn with_w(mut self, w: bool) -> Self {
self.w = w;
self
}
pub fn with_opmask(mut self, opmask: EvexOpmaskConfig) -> Self {
self.aaa = opmask.opmask_reg & 0x07;
self.z = opmask.zeroing;
self
}
pub fn with_broadcast(mut self, bcst: EvexBroadcastMode) -> Self {
self.b_bit = bcst.is_broadcast();
self
}
pub fn with_rounding(mut self, rounding: EvexRoundingMode, vl: EvexVectorLength) -> Self {
if rounding.is_active() {
self.b_bit = true;
let (lp, lv) = rounding.encode_ll_for_rounding(vl);
self.l_prime = lp;
self.l = lv;
}
self
}
pub fn vector_length(&self) -> Option<EvexVectorLength> {
EvexVectorLength::from_ll_bits(self.l_prime, self.l)
}
pub fn ll_value(&self) -> u8 {
((self.l_prime as u8) << 1) | (self.l as u8)
}
pub fn can_compress_to_vex(&self) -> bool {
self.aaa == OPMASK_NONE
&& !self.z
&& !self.b_bit
&& !self.l_prime && !self.r_prime
&& !self.x_prime
&& !self.b_prime
&& !self.v_prime
&& EvexOpcodeMap::from_mm(self.mm).map_or(false, |m| m.is_vex_equivalent())
}
pub fn uses_evex_features(&self) -> bool {
self.aaa != OPMASK_NONE
|| self.z
|| self.b_bit
|| self.l_prime
|| self.r_prime
|| self.x_prime
|| self.b_prime
|| self.v_prime
}
pub const fn prefix_byte_count(&self) -> u8 {
4
}
}
pub struct EvexPrefixBuilder;
impl EvexPrefixBuilder {
#[inline]
pub fn build_raw(
r_prime: bool,
x_prime: bool,
b_prime: bool,
r: bool,
x: bool,
b: bool,
mm: u8,
v_prime: bool,
w: bool,
vvvv: u8,
pp: u8,
z: bool,
l_prime: bool,
l: bool,
b_bit: bool,
aaa: u8,
) -> [u8; 4] {
let p0: u8 = ((!r_prime as u8) << 7)
| ((!x_prime as u8) << 6)
| ((!b_prime as u8) << 5)
| ((!r as u8) << 4)
| (mm & EVEX_P0_MM_MASK);
let p1: u8 =
((w as u8) << 7) | ((vvvv & 0x0F) << 3) | EVEX_P1_FIXED_BIT | (pp & EVEX_P1_PP_MASK);
let p2: u8 = ((z as u8) << 7)
| ((l_prime as u8) << 6)
| ((l as u8) << 5)
| ((b_bit as u8) << 4)
| ((!v_prime as u8) << 3)
| (aaa & EVEX_P2_AAA_MASK);
[EVEX_PREFIX, p0, p1, p2]
}
#[inline]
pub fn build_from_config(config: &EvexPrefixConfig) -> [u8; 4] {
Self::build_raw(
config.r_prime,
config.x_prime,
config.b_prime,
config.r,
config.x,
config.b,
config.mm,
config.v_prime,
config.w,
config.vvvv,
config.pp,
config.z,
config.l_prime,
config.l,
config.b_bit,
config.aaa,
)
}
pub fn build_vec(config: &EvexPrefixConfig) -> Vec<u8> {
Self::build_from_config(config).to_vec()
}
pub fn build_rrr(
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
w: bool,
opmask: EvexOpmaskConfig,
) -> [u8; 4] {
let mut cfg = EvexPrefixConfig::new()
.with_map(map)
.with_prefix(prefix)
.with_vector_length(vl)
.with_dest_reg(dest_reg)
.with_vvvv(src1_reg)
.with_base_reg(src2_reg)
.with_w(w)
.with_opmask(opmask);
Self::build_from_config(&cfg)
}
pub fn build_rr_broadcast(
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
dest_reg: u8,
src1_reg: u8,
mem_base_reg: u8,
w: bool,
opmask: EvexOpmaskConfig,
broadcast: EvexBroadcastMode,
) -> [u8; 4] {
let mut cfg = EvexPrefixConfig::new()
.with_map(map)
.with_prefix(prefix)
.with_vector_length(vl)
.with_dest_reg(dest_reg)
.with_vvvv(src1_reg)
.with_base_reg(mem_base_reg)
.with_w(w)
.with_opmask(opmask)
.with_broadcast(broadcast);
Self::build_from_config(&cfg)
}
pub fn build_with_rounding(
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
w: bool,
rounding: EvexRoundingMode,
) -> [u8; 4] {
let mut cfg = EvexPrefixConfig::new()
.with_map(map)
.with_prefix(prefix)
.with_vector_length(vl)
.with_dest_reg(dest_reg)
.with_vvvv(src1_reg)
.with_base_reg(src2_reg)
.with_w(w)
.with_rounding(rounding, vl);
Self::build_from_config(&cfg)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct EvexDecodedPrefix {
pub r_prime: bool,
pub x_prime: bool,
pub b_prime: bool,
pub r: bool,
pub x: bool,
pub b: bool,
pub mm: u8,
pub v_prime: bool,
pub w: bool,
pub vvvv: u8,
pub pp: u8,
pub z: bool,
pub l_prime: bool,
pub l: bool,
pub b_bit: bool,
pub aaa: u8,
}
impl EvexDecodedPrefix {
pub fn decode(p0: u8, p1: u8, p2: u8) -> Self {
Self {
r_prime: (p0 & EVEX_P0_R_PRIME_MASK) == 0,
x_prime: (p0 & EVEX_P0_X_PRIME_MASK) == 0,
b_prime: (p0 & EVEX_P0_B_PRIME_MASK) == 0,
r: (p0 & EVEX_P0_R_MASK) == 0,
x: false, b: false, mm: p0 & EVEX_P0_MM_MASK,
v_prime: (p1 & EVEX_P2_V_PRIME_MASK) == 0, w: (p1 & EVEX_P1_W_MASK) != 0,
vvvv: (p1 >> 3) & 0x0F,
pp: p1 & EVEX_P1_PP_MASK,
z: (p2 & EVEX_P2_Z_MASK) != 0,
l_prime: (p2 & EVEX_P2_L_PRIME_MASK) != 0,
l: (p2 & EVEX_P2_L_MASK) != 0,
b_bit: (p2 & EVEX_P2_B_MASK) != 0,
aaa: p2 & EVEX_P2_AAA_MASK,
}
}
pub fn decode_from_slice(bytes: &[u8]) -> Option<(Self, usize)> {
if bytes.len() < 4 || bytes[0] != EVEX_PREFIX {
return None;
}
Some((Self::decode(bytes[1], bytes[2], bytes[3]), 4))
}
pub fn vector_length(&self) -> Option<EvexVectorLength> {
EvexVectorLength::from_ll_bits(self.l_prime, self.l)
}
pub fn opmask_reg(&self) -> u8 {
self.aaa
}
pub fn is_zeroing(&self) -> bool {
self.z
}
pub fn is_broadcast(&self) -> bool {
self.b_bit
}
pub fn effective_dest_reg(&self, low_reg: u8) -> u8 {
let mut reg = low_reg & 0x07;
if !self.r {
reg |= 0x08;
}
if !self.r_prime {
reg |= 0x10;
}
reg
}
pub fn effective_vvvv_reg(&self) -> u8 {
let mut reg = vex_recover_reg(self.vvvv);
if !self.v_prime {
reg |= 0x10;
}
reg
}
pub fn effective_base_reg(&self, low_reg: u8) -> u8 {
let mut reg = low_reg & 0x07;
if !self.b {
reg |= 0x08;
}
if !self.b_prime {
reg |= 0x10;
}
reg
}
pub fn opcode_map(&self) -> Option<EvexOpcodeMap> {
EvexOpcodeMap::from_mm(self.mm)
}
pub fn mandatory_prefix(&self) -> VexMandatoryPrefix {
VexMandatoryPrefix::from_pp(self.pp).unwrap_or(VexMandatoryPrefix::None)
}
pub fn can_compress_to_vex(&self) -> bool {
let cfg = self.to_config();
cfg.can_compress_to_vex()
}
pub fn to_config(&self) -> EvexPrefixConfig {
EvexPrefixConfig {
r_prime: self.r_prime,
x_prime: self.x_prime,
b_prime: self.b_prime,
r: self.r,
x: self.x,
b: self.b,
mm: self.mm,
v_prime: self.v_prime,
w: self.w,
vvvv: self.vvvv,
pp: self.pp,
z: self.z,
l_prime: self.l_prime,
l: self.l,
b_bit: self.b_bit,
aaa: self.aaa,
}
}
}
impl fmt::Display for EvexDecodedPrefix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"EVEX R'={} X'={} B'={} R={} mm={:02b} V'={} W={} vvvv={:04b} pp={:02b} z={} L'={} L={} b={} aaa={}",
self.r_prime as u8,
self.x_prime as u8,
self.b_prime as u8,
self.r as u8,
self.mm,
self.v_prime as u8,
self.w as u8,
self.vvvv,
self.pp,
self.z as u8,
self.l_prime as u8,
self.l as u8,
self.b_bit as u8,
self.aaa,
)
}
}
#[derive(Debug, Clone)]
pub struct EvexCompressedDisplacement;
impl EvexCompressedDisplacement {
pub fn multiplier(tuple_type: EvexTupleType, vl: EvexVectorLength, element_size: u8) -> u8 {
tuple_type.multiplier(vl, element_size)
}
pub fn compress(
disp: i32,
tuple_type: EvexTupleType,
vl: EvexVectorLength,
element_size: u8,
) -> Option<u8> {
let n = Self::multiplier(tuple_type, vl, element_size) as i32;
if n == 0 {
return None;
}
if disp % n != 0 {
return None;
}
let compressed = disp / n;
if compressed >= -128 && compressed <= 127 {
Some(compressed as u8)
} else {
None
}
}
pub fn decompress(
disp8: i8,
tuple_type: EvexTupleType,
vl: EvexVectorLength,
element_size: u8,
) -> i32 {
let n = Self::multiplier(tuple_type, vl, element_size) as i32;
(disp8 as i32) * n
}
pub fn can_compress(
disp: i32,
tuple_type: EvexTupleType,
vl: EvexVectorLength,
element_size: u8,
) -> bool {
Self::compress(disp, tuple_type, vl, element_size).is_some()
}
pub fn broadcast_multiplier(element_size: u8) -> u8 {
element_size
}
pub fn compress_broadcast(disp: i32, element_size: u8) -> Option<u8> {
let n = element_size as i32;
if disp % n != 0 {
return None;
}
let compressed = disp / n;
if compressed >= -128 && compressed <= 127 {
Some(compressed as u8)
} else {
None
}
}
pub const MAX_COMPRESSED_DISP: i32 = 127;
pub const MIN_COMPRESSED_DISP: i32 = -128;
pub fn build_multiplier_table(vl: EvexVectorLength) -> Vec<(EvexTupleType, u8)> {
let element_sizes: [u8; 5] = [1, 2, 4, 8, 16];
let tuple_types = [
EvexTupleType::Full,
EvexTupleType::Half,
EvexTupleType::Quarter,
EvexTupleType::Eighth,
EvexTupleType::Scalar,
EvexTupleType::Mem128,
EvexTupleType::Tuple1Scalar,
EvexTupleType::Tuple2,
EvexTupleType::Tuple4,
EvexTupleType::Tuple8,
];
let mut table = Vec::new();
for tt in &tuple_types {
table.push((*tt, Self::multiplier(*tt, vl, 4)));
}
table
}
}
#[derive(Debug, Clone)]
pub struct EvexInstructionLength;
impl EvexInstructionLength {
pub const EVEX_PREFIX_SIZE: u8 = 4;
pub const MAX_LENGTH: u8 = 15;
pub fn compute(
opcode_size: u8,
has_modrm: bool,
has_sib: bool,
disp_size: u8, imm_size: u8,
) -> u16 {
let prefix: u16 = 4;
let op: u16 = opcode_size as u16;
let modrm: u16 = if has_modrm { 1 } else { 0 };
let sib: u16 = if has_sib { 1 } else { 0 };
let disp: u16 = disp_size as u16;
let imm: u16 = imm_size as u16;
prefix + op + modrm + sib + disp + imm
}
pub fn compute_from_config(
config: &EvexPrefixConfig,
opcode_size: u8,
has_modrm: bool,
has_sib: bool,
disp_size: u8,
imm_size: u8,
) -> u16 {
Self::compute(opcode_size, has_modrm, has_sib, disp_size, imm_size)
}
pub fn is_valid_length(length: u16) -> bool {
length <= Self::MAX_LENGTH as u16
}
pub fn estimate_common(
has_modrm: bool,
has_sib: bool,
uses_compressed_disp: bool,
has_imm8: bool,
) -> u16 {
let op: u16 = 1;
let modrm: u16 = if has_modrm { 1 } else { 0 };
let sib: u16 = if has_sib { 1 } else { 0 };
let disp: u16 = if uses_compressed_disp {
1
} else if has_modrm {
4
} else {
0
};
let imm: u16 = if has_imm8 { 1 } else { 0 };
4 + op + modrm + sib + disp + imm
}
}
#[derive(Debug, Clone)]
pub struct EvexToVexCompressor;
impl EvexToVexCompressor {
pub fn can_compress(config: &EvexPrefixConfig) -> bool {
config.can_compress_to_vex()
}
pub fn compress(config: &EvexPrefixConfig) -> Option<super::x86_vex_encoding::VexPrefixConfig> {
if !config.can_compress_to_vex() {
return None;
}
let vex_mmmmm = match config.mm {
EVEX_MM_0F => super::x86_vex_encoding::VEX_MMMMM_0F,
EVEX_MM_0F38 => super::x86_vex_encoding::VEX_MMMMM_0F38,
EVEX_MM_0F3A => super::x86_vex_encoding::VEX_MMMMM_0F3A,
_ => return None,
};
Some(super::x86_vex_encoding::VexPrefixConfig {
r: config.r,
x: config.x,
b: config.b,
w: config.w,
mmmmm: vex_mmmmm,
vvvv: config.vvvv,
l: config.l,
pp: config.pp,
})
}
pub fn compression_blockers(config: &EvexPrefixConfig) -> Vec<&'static str> {
let mut blockers = Vec::new();
if config.aaa != OPMASK_NONE {
blockers.push("opmask register active");
}
if config.z {
blockers.push("zeroing mask active");
}
if config.b_bit {
blockers.push("broadcast or rounding active");
}
if config.l_prime {
blockers.push("512-bit vector length (L'=1)");
}
if config.r_prime {
blockers.push("extended destination register (R'=1)");
}
if config.x_prime {
blockers.push("extended index register (X'=1)");
}
if config.b_prime {
blockers.push("extended base register (B'=1)");
}
if config.v_prime {
blockers.push("extended vvvv register (V'=1)");
}
if !EvexOpcodeMap::from_mm(config.mm).map_or(false, |m| m.is_vex_equivalent()) {
blockers.push("non-VEX opcode map (mm > 3)");
}
blockers
}
}
#[derive(Debug, Clone)]
pub struct EvexEncodedInstruction {
pub evex_prefix: Vec<u8>,
pub opcode: Vec<u8>,
pub modrm: Option<u8>,
pub sib: Option<u8>,
pub displacement: Vec<u8>,
pub immediate: Vec<u8>,
pub config: EvexPrefixConfig,
pub opmask: EvexOpmaskConfig,
pub broadcast: EvexBroadcastMode,
pub rounding: EvexRoundingMode,
}
impl EvexEncodedInstruction {
pub fn new(config: EvexPrefixConfig) -> Self {
Self {
evex_prefix: Vec::new(),
opcode: Vec::new(),
modrm: None,
sib: None,
displacement: Vec::new(),
immediate: Vec::new(),
config,
opmask: EvexOpmaskConfig::default(),
broadcast: EvexBroadcastMode::None,
rounding: EvexRoundingMode::None,
}
}
pub fn assemble(&self) -> Vec<u8> {
let mut bytes = Vec::with_capacity(self.total_length() as usize);
bytes.extend_from_slice(&self.evex_prefix);
bytes.extend_from_slice(&self.opcode);
if let Some(modrm) = self.modrm {
bytes.push(modrm);
}
if let Some(sib) = self.sib {
bytes.push(sib);
}
bytes.extend_from_slice(&self.displacement);
bytes.extend_from_slice(&self.immediate);
bytes
}
pub fn total_length(&self) -> u16 {
let mut len: u16 = self.evex_prefix.len() as u16;
len += self.opcode.len() as u16;
if self.modrm.is_some() {
len += 1;
}
if self.sib.is_some() {
len += 1;
}
len += self.displacement.len() as u16;
len += self.immediate.len() as u16;
len
}
pub fn is_valid(&self) -> bool {
self.total_length() <= 15
}
pub fn uses_evex_features(&self) -> bool {
self.config.uses_evex_features()
}
}
impl fmt::Display for EvexEncodedInstruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bytes = self.assemble();
write!(f, "EVEX[{}] ", bytes.len())?;
for b in &bytes {
write!(f, "{:02X} ", b)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct X86EVEXEncoding {
pub default_vector_length: EvexVectorLength,
pub default_opmask: EvexOpmaskConfig,
pub prefer_compressed_disp: bool,
pub allow_evex_to_vex_compression: bool,
}
impl Default for X86EVEXEncoding {
fn default() -> Self {
Self {
default_vector_length: EvexVectorLength::VL512,
default_opmask: EvexOpmaskConfig::none(),
prefer_compressed_disp: true,
allow_evex_to_vex_compression: true,
}
}
}
impl X86EVEXEncoding {
pub fn new() -> Self {
Self::default()
}
pub fn with_default_vl(mut self, vl: EvexVectorLength) -> Self {
self.default_vector_length = vl;
self
}
pub fn with_default_opmask(mut self, opmask: EvexOpmaskConfig) -> Self {
self.default_opmask = opmask;
self
}
pub fn with_evex_to_vex_compression(mut self, enabled: bool) -> Self {
self.allow_evex_to_vex_compression = enabled;
self
}
pub fn config_for_rrr(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
w: bool,
) -> EvexPrefixConfig {
EvexPrefixConfig::new()
.with_map(map)
.with_prefix(prefix)
.with_vector_length(vl)
.with_dest_reg(dest_reg)
.with_vvvv(src1_reg)
.with_base_reg(src2_reg)
.with_w(w)
.with_opmask(self.default_opmask)
}
pub fn config_with_opmask(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
w: bool,
opmask: EvexOpmaskConfig,
) -> EvexPrefixConfig {
let mut cfg = self.config_for_rrr(map, prefix, vl, dest_reg, src1_reg, src2_reg, w);
cfg = cfg.with_opmask(opmask);
cfg
}
pub fn config_with_broadcast(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
dest_reg: u8,
src1_reg: u8,
mem_base_reg: u8,
w: bool,
opmask: EvexOpmaskConfig,
broadcast: EvexBroadcastMode,
) -> EvexPrefixConfig {
let mut cfg = EvexPrefixConfig::new()
.with_map(map)
.with_prefix(prefix)
.with_vector_length(vl)
.with_dest_reg(dest_reg)
.with_vvvv(src1_reg)
.with_base_reg(mem_base_reg)
.with_w(w)
.with_opmask(opmask)
.with_broadcast(broadcast);
cfg
}
pub fn config_with_rounding(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
w: bool,
rounding: EvexRoundingMode,
) -> EvexPrefixConfig {
let mut cfg = self.config_for_rrr(map, prefix, vl, dest_reg, src1_reg, src2_reg, w);
cfg = cfg.with_rounding(rounding, vl);
cfg
}
pub fn encode_evex_prefix(&self, config: &EvexPrefixConfig) -> Vec<u8> {
EvexPrefixBuilder::build_vec(config)
}
pub fn encode_rrr(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
opcode: u8,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
) -> EvexEncodedInstruction {
let config = self.config_for_rrr(map, prefix, vl, dest_reg, src1_reg, src2_reg, false);
let evex_bytes = EvexPrefixBuilder::build_vec(&config);
let modrm = 0xC0 | ((dest_reg & 0x07) << 3) | (src2_reg & 0x07);
EvexEncodedInstruction {
evex_prefix: evex_bytes,
opcode: vec![opcode],
modrm: Some(modrm),
sib: None,
displacement: vec![],
immediate: vec![],
config,
opmask: self.default_opmask,
broadcast: EvexBroadcastMode::None,
rounding: EvexRoundingMode::None,
}
}
pub fn encode_zero_masked(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
opcode: u8,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
opmask_reg: u8,
) -> EvexEncodedInstruction {
let opmask = EvexOpmaskConfig::zero(opmask_reg);
let config =
self.config_with_opmask(map, prefix, vl, dest_reg, src1_reg, src2_reg, false, opmask);
let evex_bytes = EvexPrefixBuilder::build_vec(&config);
let modrm = 0xC0 | ((dest_reg & 0x07) << 3) | (src2_reg & 0x07);
EvexEncodedInstruction {
evex_prefix: evex_bytes,
opcode: vec![opcode],
modrm: Some(modrm),
sib: None,
displacement: vec![],
immediate: vec![],
config,
opmask,
broadcast: EvexBroadcastMode::None,
rounding: EvexRoundingMode::None,
}
}
pub fn encode_merge_masked(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
opcode: u8,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
opmask_reg: u8,
) -> EvexEncodedInstruction {
let opmask = EvexOpmaskConfig::merge(opmask_reg);
let config =
self.config_with_opmask(map, prefix, vl, dest_reg, src1_reg, src2_reg, false, opmask);
let evex_bytes = EvexPrefixBuilder::build_vec(&config);
let modrm = 0xC0 | ((dest_reg & 0x07) << 3) | (src2_reg & 0x07);
EvexEncodedInstruction {
evex_prefix: evex_bytes,
opcode: vec![opcode],
modrm: Some(modrm),
sib: None,
displacement: vec![],
immediate: vec![],
config,
opmask,
broadcast: EvexBroadcastMode::None,
rounding: EvexRoundingMode::None,
}
}
pub fn encode_broadcast(
&self,
map: EvexOpcodeMap,
prefix: VexMandatoryPrefix,
vl: EvexVectorLength,
opcode: u8,
dest_reg: u8,
src1_reg: u8,
mem_base_reg: u8,
broadcast: EvexBroadcastMode,
opmask: EvexOpmaskConfig,
disp: i32,
element_size: u8,
) -> EvexEncodedInstruction {
let config = self.config_with_broadcast(
map,
prefix,
vl,
dest_reg,
src1_reg,
mem_base_reg,
false,
opmask,
broadcast,
);
let evex_bytes = EvexPrefixBuilder::build_vec(&config);
let modrm = ((dest_reg & 0x07) << 3) | (mem_base_reg & 0x07);
let disp_bytes =
if let Some(d8) = EvexCompressedDisplacement::compress_broadcast(disp, element_size) {
vec![d8]
} else {
disp.to_le_bytes().to_vec()
};
EvexEncodedInstruction {
evex_prefix: evex_bytes,
opcode: vec![opcode],
modrm: Some(modrm),
sib: None,
displacement: disp_bytes,
immediate: vec![],
config,
opmask,
broadcast,
rounding: EvexRoundingMode::None,
}
}
pub fn decode_evex(&self, bytes: &[u8]) -> Option<(EvexDecodedPrefix, usize)> {
EvexDecodedPrefix::decode_from_slice(bytes)
}
pub fn compress_displacement(
&self,
disp: i32,
tuple_type: EvexTupleType,
vl: EvexVectorLength,
element_size: u8,
) -> Option<u8> {
EvexCompressedDisplacement::compress(disp, tuple_type, vl, element_size)
}
pub fn decompress_displacement(
&self,
disp8: i8,
tuple_type: EvexTupleType,
vl: EvexVectorLength,
element_size: u8,
) -> i32 {
EvexCompressedDisplacement::decompress(disp8, tuple_type, vl, element_size)
}
pub fn try_compress_to_vex(
&self,
config: &EvexPrefixConfig,
) -> Option<super::x86_vex_encoding::VexPrefixConfig> {
if self.allow_evex_to_vex_compression {
EvexToVexCompressor::compress(config)
} else {
None
}
}
pub fn evex_compression_blockers(&self, config: &EvexPrefixConfig) -> Vec<&'static str> {
EvexToVexCompressor::compression_blockers(config)
}
pub fn compute_length(
&self,
opcode_size: u8,
has_modrm: bool,
has_sib: bool,
disp_size: u8,
imm_size: u8,
) -> u16 {
EvexInstructionLength::compute(opcode_size, has_modrm, has_sib, disp_size, imm_size)
}
pub fn tuple_multiplier(
&self,
tuple_type: EvexTupleType,
vl: EvexVectorLength,
element_size: u8,
) -> u8 {
tuple_type.multiplier(vl, element_size)
}
pub fn select_tuple_type(&self, element_size: u8, memory_only: bool) -> EvexTupleType {
if memory_only {
EvexTupleType::FullMem
} else {
EvexTupleType::Full
}
}
}
#[derive(Debug, Clone)]
pub struct EvexAssemblyPrinter;
impl EvexAssemblyPrinter {
pub fn format_prefix(decoded: &EvexDecodedPrefix) -> String {
let mut s = String::from("EVEX");
if decoded.r_prime {
s.push_str(" R'");
}
if decoded.x_prime {
s.push_str(" X'");
}
if decoded.b_prime {
s.push_str(" B'");
}
if decoded.w {
s.push_str(" W");
}
if decoded.z {
s.push_str(" {z}");
}
if let Some(vl) = decoded.vector_length() {
s.push_str(&format!(" VL={}", vl.width_bits()));
}
if decoded.aaa != 0 {
s.push_str(&format!(" {{k{}}}", decoded.aaa));
}
if decoded.b_bit && decoded.aaa == 0 {
s.push_str(" {bcst}");
}
s
}
pub fn print_intel_syntax(
mnemonic: &str,
decoded: &EvexDecodedPrefix,
dest_reg: u8,
src1_reg: u8,
src2_reg: u8,
) -> String {
let vl = decoded.vector_length();
let reg_prefix = vl.map_or("xmm", |v| v.reg_prefix());
let mut s = format!(
"{} {}{}, {}{}, {}{}",
mnemonic,
reg_prefix,
decoded.effective_dest_reg(dest_reg),
reg_prefix,
decoded.effective_vvvv_reg(),
reg_prefix,
decoded.effective_base_reg(src2_reg),
);
if decoded.aaa != 0 {
s.push_str(&format!(" {{k{}}}", decoded.aaa));
}
if decoded.z {
s.push_str(" {z}");
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_evex_constants() {
assert_eq!(EVEX_PREFIX, 0x62);
assert_eq!(EVEX_MM_0F, 0x01);
assert_eq!(EVEX_MM_0F38, 0x02);
assert_eq!(EVEX_MM_0F3A, 0x03);
assert_eq!(EVEX_MM_5, 0x05);
assert_eq!(EVEX_MM_6, 0x06);
assert_eq!(EVEX_MM_7, 0x07);
assert_eq!(EVEX_LL_128, 0x00);
assert_eq!(EVEX_LL_256, 0x01);
assert_eq!(EVEX_LL_512, 0x02);
}
#[test]
fn test_evex_opcode_map_mm() {
assert_eq!(EvexOpcodeMap::Map0F.mm(), 1);
assert_eq!(EvexOpcodeMap::Map0F38.mm(), 2);
assert_eq!(EvexOpcodeMap::Map0F3A.mm(), 3);
assert_eq!(EvexOpcodeMap::Map5.mm(), 5);
assert_eq!(EvexOpcodeMap::Map6.mm(), 6);
assert_eq!(EvexOpcodeMap::Map7.mm(), 7);
}
#[test]
fn test_evex_opcode_map_from_mm() {
assert_eq!(EvexOpcodeMap::from_mm(1), Some(EvexOpcodeMap::Map0F));
assert_eq!(EvexOpcodeMap::from_mm(2), Some(EvexOpcodeMap::Map0F38));
assert_eq!(EvexOpcodeMap::from_mm(0), None);
assert_eq!(EvexOpcodeMap::from_mm(4), None);
}
#[test]
fn test_evex_opcode_map_is_vex_equivalent() {
assert!(EvexOpcodeMap::Map0F.is_vex_equivalent());
assert!(EvexOpcodeMap::Map0F38.is_vex_equivalent());
assert!(EvexOpcodeMap::Map0F3A.is_vex_equivalent());
assert!(!EvexOpcodeMap::Map5.is_vex_equivalent());
assert!(!EvexOpcodeMap::Map6.is_vex_equivalent());
}
#[test]
fn test_evex_vector_length_ll_bits() {
assert_eq!(EvexVectorLength::VL128.ll_bits(), (false, false));
assert_eq!(EvexVectorLength::VL256.ll_bits(), (false, true));
assert_eq!(EvexVectorLength::VL512.ll_bits(), (true, false));
}
#[test]
fn test_evex_vector_length_from_ll() {
assert_eq!(
EvexVectorLength::from_ll_bits(false, false),
Some(EvexVectorLength::VL128)
);
assert_eq!(
EvexVectorLength::from_ll_bits(false, true),
Some(EvexVectorLength::VL256)
);
assert_eq!(
EvexVectorLength::from_ll_bits(true, false),
Some(EvexVectorLength::VL512)
);
assert_eq!(EvexVectorLength::from_ll_bits(true, true), None);
}
#[test]
fn test_evex_vector_length_width() {
assert_eq!(EvexVectorLength::VL128.width_bytes(), 16);
assert_eq!(EvexVectorLength::VL256.width_bytes(), 32);
assert_eq!(EvexVectorLength::VL512.width_bytes(), 64);
assert_eq!(EvexVectorLength::VL128.reg_prefix(), "xmm");
assert_eq!(EvexVectorLength::VL256.reg_prefix(), "ymm");
assert_eq!(EvexVectorLength::VL512.reg_prefix(), "zmm");
}
#[test]
fn test_evex_tuple_type_multiplier() {
let vl = EvexVectorLength::VL512;
assert_eq!(EvexTupleType::Full.multiplier(vl, 4), 4);
assert_eq!(EvexTupleType::Full.multiplier(vl, 8), 8);
assert_eq!(EvexTupleType::Half.multiplier(vl, 4), 32);
assert_eq!(EvexTupleType::Quarter.multiplier(vl, 4), 16);
assert_eq!(EvexTupleType::Eighth.multiplier(vl, 4), 8);
assert_eq!(EvexTupleType::Mem128.multiplier(vl, 4), 16);
assert_eq!(EvexTupleType::Scalar.multiplier(vl, 4), 4);
}
#[test]
fn test_evex_tuple_type_is_memory() {
assert!(EvexTupleType::FullMem.is_memory());
assert!(EvexTupleType::HalfMem.is_memory());
assert!(!EvexTupleType::Full.is_memory());
assert!(!EvexTupleType::Scalar.is_memory());
}
#[test]
fn test_evex_rounding_mode_rc_bits() {
assert_eq!(EvexRoundingMode::RnSae.rc_bits(), 0b00);
assert_eq!(EvexRoundingMode::RdSae.rc_bits(), 0b01);
assert_eq!(EvexRoundingMode::RuSae.rc_bits(), 0b10);
assert_eq!(EvexRoundingMode::RzSae.rc_bits(), 0b11);
assert_eq!(EvexRoundingMode::None.rc_bits(), 0);
}
#[test]
fn test_evex_rounding_mode_is_active() {
assert!(EvexRoundingMode::RnSae.is_active());
assert!(!EvexRoundingMode::None.is_active());
}
#[test]
fn test_evex_rounding_mode_has_sae() {
assert!(EvexRoundingMode::RnSae.has_sae());
assert!(EvexRoundingMode::Sae.has_sae());
assert!(!EvexRoundingMode::None.has_sae());
}
#[test]
fn test_evex_broadcast_mode() {
assert!(EvexBroadcastMode::Broadcast1To8.is_broadcast());
assert!(!EvexBroadcastMode::None.is_broadcast());
assert_eq!(EvexBroadcastMode::Broadcast1To4.element_count(), 4);
assert_eq!(EvexBroadcastMode::Broadcast1To16.element_count(), 16);
assert!(EvexBroadcastMode::Broadcast1To2.to_b_bit());
assert!(!EvexBroadcastMode::None.to_b_bit());
}
#[test]
fn test_evex_opmask_config() {
let none = EvexOpmaskConfig::none();
assert!(!none.has_active_mask());
assert!(!none.has_zeroing());
let merge = EvexOpmaskConfig::merge(3);
assert!(merge.has_active_mask());
assert!(!merge.has_zeroing());
assert!(merge.has_merging());
let zero = EvexOpmaskConfig::zero(5);
assert!(zero.has_active_mask());
assert!(zero.has_zeroing());
assert!(zero.suffix().contains("k5"));
assert!(zero.suffix().contains("{z}"));
}
#[test]
fn test_evex_prefix_config_default() {
let cfg = EvexPrefixConfig::default();
assert_eq!(cfg.mm, EVEX_MM_0F);
assert_eq!(cfg.pp, EVEX_PP_NONE);
assert_eq!(cfg.aaa, OPMASK_NONE);
}
#[test]
fn test_evex_prefix_config_can_compress_to_vex() {
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F)
.with_vector_length(EvexVectorLength::VL128);
assert!(cfg.can_compress_to_vex());
let cfg = cfg.with_opmask(EvexOpmaskConfig::merge(1));
assert!(!cfg.can_compress_to_vex());
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F)
.with_vector_length(EvexVectorLength::VL512);
assert!(!cfg.can_compress_to_vex());
let cfg = EvexPrefixConfig::new().with_map(EvexOpcodeMap::Map5);
assert!(!cfg.can_compress_to_vex());
}
#[test]
fn test_evex_prefix_config_vector_length() {
let cfg = EvexPrefixConfig::new().with_vector_length(EvexVectorLength::VL256);
assert_eq!(cfg.vector_length(), Some(EvexVectorLength::VL256));
assert_eq!(cfg.ll_value(), 1);
}
#[test]
fn test_build_evex_basic() {
let bytes = EvexPrefixBuilder::build_raw(
false,
false,
false, false,
false,
false, EVEX_MM_0F, false,
false, 0x0E, EVEX_PP_NONE, false,
false,
false,
false, 0, );
assert_eq!(bytes[0], 0x62);
assert_eq!(bytes[1], 0xF1);
assert_eq!(bytes[2], 0x74);
assert_eq!(bytes[3], 0x08);
}
#[test]
fn test_build_evex_with_mask() {
let bytes = EvexPrefixBuilder::build_raw(
false,
false,
false,
false,
false,
false,
EVEX_MM_0F,
false,
false,
0x0E,
EVEX_PP_NONE,
true,
false,
false,
false, 3, );
assert_eq!(bytes[0], 0x62);
assert_eq!(bytes[3], 0x8B);
}
#[test]
fn test_build_evex_512() {
let bytes = EvexPrefixBuilder::build_raw(
false,
false,
false,
false,
false,
false,
EVEX_MM_0F38,
false,
false,
0x0E,
EVEX_PP_66,
false,
true,
false,
false, 0,
);
assert_eq!(bytes[3] & 0x60, 0x40); }
#[test]
fn test_build_evex_rrr() {
let bytes = EvexPrefixBuilder::build_rrr(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::None,
EvexVectorLength::VL128,
0,
1,
2, false,
EvexOpmaskConfig::none(),
);
assert_eq!(bytes[0], 0x62);
assert_eq!(bytes.len(), 4);
}
#[test]
fn test_build_evex_with_broadcast() {
let bytes = EvexPrefixBuilder::build_rr_broadcast(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::P66,
EvexVectorLength::VL512,
0,
1,
2,
false,
EvexOpmaskConfig::merge(1),
EvexBroadcastMode::Broadcast1To16,
);
assert_eq!(bytes[0], 0x62);
assert!(bytes[3] & EVEX_P2_B_MASK != 0);
}
#[test]
fn test_decode_evex_128() {
let decoded = EvexDecodedPrefix::decode(0xF1, 0x74, 0x08);
assert_eq!(decoded.mm, 1);
assert_eq!(decoded.pp, 0);
assert_eq!(decoded.vector_length(), Some(EvexVectorLength::VL128));
assert!(!decoded.z);
assert!(!decoded.b_bit);
assert_eq!(decoded.aaa, 0);
}
#[test]
fn test_decode_evex_512() {
let decoded = EvexDecodedPrefix::decode(0xF1, 0x74, 0x48);
assert_eq!(decoded.vector_length(), Some(EvexVectorLength::VL512));
}
#[test]
fn test_decode_evex_with_mask() {
let decoded = EvexDecodedPrefix::decode(0xF1, 0x74, 0x8B);
assert!(decoded.z);
assert_eq!(decoded.aaa, 3);
}
#[test]
fn test_decode_evex_from_slice() {
let (d, len) = EvexDecodedPrefix::decode_from_slice(&[0x62, 0xF1, 0x74, 0x08]).unwrap();
assert_eq!(len, 4);
assert!(!d.z);
assert!(EvexDecodedPrefix::decode_from_slice(&[0xC5, 0xF8]).is_none());
assert!(EvexDecodedPrefix::decode_from_slice(&[]).is_none());
}
#[test]
fn test_evex_decoded_effective_regs() {
let decoded = EvexDecodedPrefix::decode(0xF1, 0x74, 0x08);
assert_eq!(decoded.effective_dest_reg(3), 3);
let decoded = EvexDecodedPrefix::decode(0xE1, 0x74, 0x08);
assert_eq!(decoded.effective_dest_reg(3), 11);
}
#[test]
fn test_evex_compress_disp() {
let vl = EvexVectorLength::VL512;
let compressed = EvexCompressedDisplacement::compress(64, EvexTupleType::Full, vl, 4);
assert_eq!(compressed, Some(16));
assert!(EvexCompressedDisplacement::compress(65, EvexTupleType::Full, vl, 4).is_none());
assert!(EvexCompressedDisplacement::compress(1024, EvexTupleType::Full, vl, 4).is_none());
}
#[test]
fn test_evex_decompress_disp() {
let vl = EvexVectorLength::VL512;
let decompressed = EvexCompressedDisplacement::decompress(16i8, EvexTupleType::Full, vl, 4);
assert_eq!(decompressed, 64);
let decompressed =
EvexCompressedDisplacement::decompress(-4i8, EvexTupleType::Quarter, vl, 4);
assert_eq!(decompressed, -64);
}
#[test]
fn test_evex_broadcast_compress_disp() {
let compressed = EvexCompressedDisplacement::compress_broadcast(64, 4);
assert_eq!(compressed, Some(16));
let compressed = EvexCompressedDisplacement::compress_broadcast(65, 4);
assert!(compressed.is_none());
}
#[test]
fn test_evex_to_vex_compress() {
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F)
.with_vector_length(EvexVectorLength::VL128);
let vex = EvexToVexCompressor::compress(&cfg);
assert!(vex.is_some());
assert_eq!(vex.unwrap().mmmmm, super::x86_vex_encoding::VEX_MMMMM_0F);
}
#[test]
fn test_evex_to_vex_cannot_compress_512() {
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F)
.with_vector_length(EvexVectorLength::VL512);
assert!(EvexToVexCompressor::compress(&cfg).is_none());
}
#[test]
fn test_evex_to_vex_blockers() {
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map5)
.with_vector_length(EvexVectorLength::VL512)
.with_opmask(EvexOpmaskConfig::merge(1));
let blockers = EvexToVexCompressor::compression_blockers(&cfg);
assert!(blockers.len() >= 2);
}
#[test]
fn test_evex_instruction_length() {
let len = EvexInstructionLength::compute(1, true, false, 0, 0);
assert_eq!(len, 6);
let len = EvexInstructionLength::compute(1, true, false, 1, 1);
assert_eq!(len, 8);
let len = EvexInstructionLength::compute(1, true, true, 4, 0);
assert_eq!(len, 11);
}
#[test]
fn test_evex_max_length() {
assert!(EvexInstructionLength::is_valid_length(15));
assert!(!EvexInstructionLength::is_valid_length(16));
}
#[test]
fn test_x86_evex_encoding_default() {
let enc = X86EVEXEncoding::new();
assert_eq!(enc.default_vector_length, EvexVectorLength::VL512);
assert!(!enc.default_opmask.has_active_mask());
assert!(enc.prefer_compressed_disp);
assert!(enc.allow_evex_to_vex_compression);
}
#[test]
fn test_x86_evex_encoding_encode_rrr() {
let enc = X86EVEXEncoding::new();
let instr = enc.encode_rrr(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::None,
EvexVectorLength::VL128,
0x58, 0,
1,
2,
);
assert_eq!(instr.opcode, vec![0x58]);
assert!(instr.modrm.is_some());
assert_eq!(instr.evex_prefix.len(), 4);
assert!(instr.is_valid());
}
#[test]
fn test_x86_evex_encoding_encode_zero_masked() {
let enc = X86EVEXEncoding::new();
let instr = enc.encode_zero_masked(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::P66,
EvexVectorLength::VL512,
0xFE, 0,
1,
2,
3, );
assert!(instr.config.z);
assert_eq!(instr.config.aaa, 3);
assert_eq!(instr.opmask.opmask_reg, 3);
assert!(instr.opmask.zeroing);
}
#[test]
fn test_x86_evex_encoding_encode_merge_masked() {
let enc = X86EVEXEncoding::new();
let instr = enc.encode_merge_masked(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::P66,
EvexVectorLength::VL512,
0xFE,
0,
1,
2,
5,
);
assert!(!instr.config.z);
assert_eq!(instr.config.aaa, 5);
assert!(instr.opmask.has_merging());
}
#[test]
fn test_x86_evex_encoding_encode_broadcast() {
let enc = X86EVEXEncoding::new();
let instr = enc.encode_broadcast(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::P66,
EvexVectorLength::VL512,
0x58,
0,
1,
2,
EvexBroadcastMode::Broadcast1To16,
EvexOpmaskConfig::merge(1),
64,
4,
);
assert!(instr.broadcast.is_broadcast());
assert!(instr.config.b_bit);
assert_eq!(instr.displacement, vec![0x10]);
}
#[test]
fn test_x86_evex_encoding_config_with_rounding() {
let enc = X86EVEXEncoding::new();
let cfg = enc.config_with_rounding(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::None,
EvexVectorLength::VL512,
0,
1,
2,
false,
EvexRoundingMode::RnSae,
);
assert!(cfg.b_bit);
assert_eq!(cfg.l_prime, true);
assert_eq!(cfg.l, false);
}
#[test]
fn test_evex_assembly_printer() {
let decoded = EvexDecodedPrefix::decode(0xF1, 0x74, 0x8B);
let s = EvexAssemblyPrinter::format_prefix(&decoded);
assert!(s.contains("EVEX"));
assert!(s.contains("{z}"));
assert!(s.contains("{k3}"));
}
#[test]
fn test_evex_assembly_printer_intel() {
let decoded = EvexDecodedPrefix::decode(0xF1, 0x74, 0x08);
let s = EvexAssemblyPrinter::print_intel_syntax("vaddps", &decoded, 0, 1, 2);
assert!(s.contains("vaddps"));
assert!(s.contains("xmm0"));
}
#[test]
fn test_roundtrip_evex_encode_decode() {
let bytes = EvexPrefixBuilder::build_rrr(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::None,
EvexVectorLength::VL128,
0,
1,
2,
false,
EvexOpmaskConfig::none(),
);
let (decoded, len) = EvexDecodedPrefix::decode_from_slice(&bytes).unwrap();
assert_eq!(len, 4);
assert_eq!(decoded.mm, EVEX_MM_0F);
assert_eq!(decoded.vector_length(), Some(EvexVectorLength::VL128));
assert!(!decoded.z);
assert_eq!(decoded.aaa, 0);
}
#[test]
fn test_full_instruction_assemble() {
let config = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F)
.with_vector_length(EvexVectorLength::VL128)
.with_vvvv(1)
.with_dest_reg(0)
.with_base_reg(2);
let mut instr = EvexEncodedInstruction::new(config);
instr.evex_prefix = EvexPrefixBuilder::build_vec(&config);
instr.opcode = vec![0x58];
instr.modrm = Some(0xC0 | ((0 & 0x07) << 3) | (2 & 0x07));
let assembled = instr.assemble();
assert_eq!(assembled.len(), 6); assert!(instr.is_valid());
}
#[test]
fn test_evex_uses_evex_features() {
let cfg = EvexPrefixConfig::new().with_map(EvexOpcodeMap::Map5);
assert!(cfg.uses_evex_features());
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F)
.with_vector_length(EvexVectorLength::VL128);
assert!(!cfg.uses_evex_features());
}
#[test]
fn test_select_tuple_type() {
let enc = X86EVEXEncoding::new();
let tt = enc.select_tuple_type(4, false);
assert_eq!(tt, EvexTupleType::Full);
let tt = enc.select_tuple_type(8, true);
assert_eq!(tt, EvexTupleType::FullMem);
}
#[test]
fn test_rounding_mode_encode_ll() {
let (lp, l) = EvexRoundingMode::RnSae.encode_ll_for_rounding(EvexVectorLength::VL512);
assert_eq!((lp, l), (true, false));
let (lp, l) = EvexRoundingMode::RnSae.encode_ll_for_rounding(EvexVectorLength::VL256);
assert_eq!((lp, l), (false, true));
}
#[test]
fn test_all_evex_opcode_maps() {
let maps = [1u8, 2, 3, 5, 6, 7];
for &mm in &maps {
assert!(EvexOpcodeMap::from_mm(mm).is_some());
}
assert!(EvexOpcodeMap::from_mm(0).is_none());
assert!(EvexOpcodeMap::from_mm(4).is_none());
}
#[test]
fn test_evex_config_with_all_features() {
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F38)
.with_prefix(VexMandatoryPrefix::P66)
.with_vector_length(EvexVectorLength::VL512)
.with_dest_reg(16) .with_vvvv(20) .with_base_reg(18) .with_index_reg(17) .with_w(true)
.with_opmask(EvexOpmaskConfig::zero(5))
.with_broadcast(EvexBroadcastMode::Broadcast1To16);
assert!(cfg.r_prime);
assert!(!cfg.r);
assert!(cfg.v_prime);
assert!(cfg.b_prime);
assert!(!cfg.b);
assert!(cfg.x_prime);
assert!(!cfg.x);
assert!(cfg.w);
assert_eq!(cfg.aaa, 5);
assert!(cfg.z);
assert!(cfg.b_bit);
assert!(!cfg.can_compress_to_vex());
}
#[test]
fn test_evex_vl128_config() {
let cfg = EvexPrefixConfig::new().with_vector_length(EvexVectorLength::VL128);
assert_eq!(cfg.ll_value(), 0);
assert!(!cfg.l_prime);
assert!(!cfg.l);
}
#[test]
fn test_evex_vl256_config() {
let cfg = EvexPrefixConfig::new().with_vector_length(EvexVectorLength::VL256);
assert_eq!(cfg.ll_value(), 1);
assert!(!cfg.l_prime);
assert!(cfg.l);
}
#[test]
fn test_evex_vl512_config() {
let cfg = EvexPrefixConfig::new().with_vector_length(EvexVectorLength::VL512);
assert_eq!(cfg.ll_value(), 2);
assert!(cfg.l_prime);
assert!(!cfg.l);
}
#[test]
fn test_evex_with_ll_direct() {
let cfg = EvexPrefixConfig::new().with_ll(1);
assert_eq!(cfg.ll_value(), 1);
assert!(!cfg.l_prime);
assert!(cfg.l);
let cfg = EvexPrefixConfig::new().with_ll(2);
assert_eq!(cfg.ll_value(), 2);
assert!(cfg.l_prime);
assert!(!cfg.l);
}
#[test]
fn test_evex_vector_length_to_vex() {
assert_eq!(EvexVectorLength::VL128.to_vex_vl(), VexVectorLength::L128);
assert_eq!(EvexVectorLength::VL256.to_vex_vl(), VexVectorLength::L256);
assert_eq!(EvexVectorLength::VL512.to_vex_vl(), VexVectorLength::L256);
}
#[test]
fn test_evex_vector_length_ll_value() {
assert_eq!(EvexVectorLength::VL128.ll_value(), 0);
assert_eq!(EvexVectorLength::VL256.ll_value(), 1);
assert_eq!(EvexVectorLength::VL512.ll_value(), 2);
}
#[test]
fn test_evex_tuple_multiplier_all_vls() {
for vl in [
EvexVectorLength::VL128,
EvexVectorLength::VL256,
EvexVectorLength::VL512,
] {
assert_eq!(EvexTupleType::Full.multiplier(vl, 4), 4);
let expected = vl.width_bytes() as u32 / 2;
assert_eq!(
EvexTupleType::Half.multiplier(vl, 4) as u32,
expected.min(255)
);
assert_eq!(EvexTupleType::Mem128.multiplier(vl, 4), 16);
assert_eq!(EvexTupleType::Scalar.multiplier(vl, 8), 8);
}
}
#[test]
fn test_evex_broadcast_all_modes() {
assert!(!EvexBroadcastMode::None.is_broadcast());
assert!(EvexBroadcastMode::Broadcast1To2.is_broadcast());
assert!(EvexBroadcastMode::Broadcast1To4.is_broadcast());
assert!(EvexBroadcastMode::Broadcast1To8.is_broadcast());
assert!(EvexBroadcastMode::Broadcast1To16.is_broadcast());
assert!(EvexBroadcastMode::Broadcast1To32.is_broadcast());
assert_eq!(EvexBroadcastMode::Broadcast1To2.element_count(), 2);
assert_eq!(EvexBroadcastMode::Broadcast1To4.element_count(), 4);
assert_eq!(EvexBroadcastMode::Broadcast1To8.element_count(), 8);
assert_eq!(EvexBroadcastMode::Broadcast1To16.element_count(), 16);
assert_eq!(EvexBroadcastMode::Broadcast1To32.element_count(), 32);
assert_eq!(EvexBroadcastMode::None.element_count(), 1);
}
#[test]
fn test_evex_broadcast_suffixes() {
assert_eq!(EvexBroadcastMode::None.suffix(), "");
assert_eq!(EvexBroadcastMode::Broadcast1To2.suffix(), "{1to2}");
assert_eq!(EvexBroadcastMode::Broadcast1To4.suffix(), "{1to4}");
assert_eq!(EvexBroadcastMode::Broadcast1To8.suffix(), "{1to8}");
assert_eq!(EvexBroadcastMode::Broadcast1To16.suffix(), "{1to16}");
assert_eq!(EvexBroadcastMode::Broadcast1To32.suffix(), "{1to32}");
}
#[test]
fn test_evex_rounding_all_modes() {
assert!(!EvexRoundingMode::None.is_active());
assert!(EvexRoundingMode::RnSae.is_active());
assert!(EvexRoundingMode::RdSae.is_active());
assert!(EvexRoundingMode::RuSae.is_active());
assert!(EvexRoundingMode::RzSae.is_active());
assert!(EvexRoundingMode::Sae.is_active());
assert!(!EvexRoundingMode::None.has_sae());
assert!(EvexRoundingMode::RnSae.has_sae());
assert!(EvexRoundingMode::Sae.has_sae());
assert!(!EvexRoundingMode::None.has_explicit_rc());
assert!(EvexRoundingMode::RnSae.has_explicit_rc());
assert!(!EvexRoundingMode::Sae.has_explicit_rc());
}
#[test]
fn test_evex_rounding_suffixes() {
assert_eq!(EvexRoundingMode::None.suffix(), "");
assert_eq!(EvexRoundingMode::RnSae.suffix(), "{rn-sae}");
assert_eq!(EvexRoundingMode::RdSae.suffix(), "{rd-sae}");
assert_eq!(EvexRoundingMode::RuSae.suffix(), "{ru-sae}");
assert_eq!(EvexRoundingMode::RzSae.suffix(), "{rz-sae}");
assert_eq!(EvexRoundingMode::Sae.suffix(), "{sae}");
}
#[test]
fn test_evex_opmask_k0_no_mask() {
let cfg = EvexOpmaskConfig::none();
assert!(!cfg.has_active_mask());
assert!(!cfg.has_zeroing());
assert!(!cfg.has_merging());
assert_eq!(cfg.opmask_reg, 0);
}
#[test]
fn test_evex_opmask_k1_merge() {
let cfg = EvexOpmaskConfig::merge(1);
assert!(cfg.has_active_mask());
assert!(!cfg.has_zeroing());
assert!(cfg.has_merging());
}
#[test]
fn test_evex_opmask_k7_zero() {
let cfg = EvexOpmaskConfig::zero(7);
assert!(cfg.has_active_mask());
assert!(cfg.has_zeroing());
assert!(!cfg.has_merging());
}
#[test]
fn test_evex_opmask_new() {
let cfg = EvexOpmaskConfig::new(3, true);
assert_eq!(cfg.opmask_reg, 3);
assert!(cfg.zeroing);
}
#[test]
fn test_evex_compress_disp_negative() {
let vl = EvexVectorLength::VL512;
let compressed = EvexCompressedDisplacement::compress(-64, EvexTupleType::Full, vl, 4);
assert_eq!(compressed, Some((-16i8) as u8)); }
#[test]
fn test_evex_compress_disp_edge() {
let vl = EvexVectorLength::VL512;
let max_disp = 127i32 * 4; let compressed = EvexCompressedDisplacement::compress(max_disp, EvexTupleType::Full, vl, 4);
assert_eq!(compressed, Some(127));
let min_disp = -128i32 * 4; let compressed = EvexCompressedDisplacement::compress(min_disp, EvexTupleType::Full, vl, 4);
assert_eq!(compressed, Some((-128i8) as u8));
assert!(EvexCompressedDisplacement::compress(512, EvexTupleType::Full, vl, 4).is_none());
assert!(EvexCompressedDisplacement::compress(-516, EvexTupleType::Full, vl, 4).is_none());
}
#[test]
fn test_evex_broadcast_disp_compression() {
assert_eq!(
EvexCompressedDisplacement::compress_broadcast(64, 4),
Some(16)
);
assert_eq!(
EvexCompressedDisplacement::compress_broadcast(64, 8),
Some(8)
);
assert!(EvexCompressedDisplacement::compress_broadcast(65, 4).is_none());
}
#[test]
fn test_evex_compressed_disp_table() {
let table = EvexCompressedDisplacement::build_multiplier_table(EvexVectorLength::VL512);
assert!(!table.is_empty());
for (_, n) in &table {
assert!(*n > 0);
}
}
#[test]
fn test_evex_to_vex_compression_map_types() {
let cfg = EvexPrefixConfig::new().with_map(EvexOpcodeMap::Map5);
assert!(EvexToVexCompressor::compress(&cfg).is_none());
let cfg = EvexPrefixConfig::new().with_map(EvexOpcodeMap::Map6);
assert!(EvexToVexCompressor::compress(&cfg).is_none());
let cfg = EvexPrefixConfig::new().with_map(EvexOpcodeMap::Map7);
assert!(EvexToVexCompressor::compress(&cfg).is_none());
}
#[test]
fn test_evex_to_vex_blocker_count() {
let cfg = EvexPrefixConfig::new()
.with_map(EvexOpcodeMap::Map0F)
.with_vector_length(EvexVectorLength::VL128);
assert!(EvexToVexCompressor::compression_blockers(&cfg).is_empty());
assert!(EvexToVexCompressor::compress(&cfg).is_some());
}
#[test]
fn test_evex_instruction_length_estimate() {
let len = EvexInstructionLength::estimate_common(true, false, true, false);
assert_eq!(len, 7);
let len = EvexInstructionLength::estimate_common(false, false, false, false);
assert_eq!(len, 5);
}
#[test]
fn test_evex_encoded_instruction_display() {
let config = EvexPrefixConfig::new().with_map(EvexOpcodeMap::Map0F);
let mut instr = EvexEncodedInstruction::new(config);
instr.evex_prefix = vec![0x62, 0xF1, 0x74, 0x08];
instr.opcode = vec![0x58];
instr.modrm = Some(0xC0);
let s = format!("{}", instr);
assert!(s.contains("EVEX"));
assert!(s.contains("62"));
}
#[test]
fn test_evex_decoded_display() {
let d = EvexDecodedPrefix::decode(0xF1, 0x74, 0x8B);
let s = format!("{}", d);
assert!(s.contains("EVEX"));
assert!(s.contains("R'="));
assert!(s.contains("mm="));
}
#[test]
fn test_evex_opcode_map_display() {
assert_eq!(format!("{}", EvexOpcodeMap::Map0F), "0F");
assert_eq!(format!("{}", EvexOpcodeMap::Map5), "MAP5");
}
#[test]
fn test_evex_vector_length_display() {
assert_eq!(format!("{}", EvexVectorLength::VL128), "128");
assert_eq!(format!("{}", EvexVectorLength::VL256), "256");
assert_eq!(format!("{}", EvexVectorLength::VL512), "512");
}
#[test]
fn test_evex_rounding_mode_display() {
assert_eq!(format!("{}", EvexRoundingMode::None), "");
assert_eq!(format!("{}", EvexRoundingMode::RnSae), "{rn-sae}");
}
#[test]
fn test_evex_broadcast_mode_display() {
assert_eq!(format!("{}", EvexBroadcastMode::None), "");
assert_eq!(format!("{}", EvexBroadcastMode::Broadcast1To8), "{1to8}");
}
#[test]
fn test_evex_opmask_config_display() {
assert_eq!(format!("{}", EvexOpmaskConfig::none()), "");
let s = format!("{}", EvexOpmaskConfig::zero(3));
assert!(s.contains("{k3}"));
assert!(s.contains("{z}"));
}
#[test]
fn test_evex_tuple_type_display() {
assert_eq!(format!("{}", EvexTupleType::Full), "Full");
assert_eq!(format!("{}", EvexTupleType::Mem128), "Mem128");
}
#[test]
fn test_evex_encoding_with_all_rounding_combinations() {
let enc = X86EVEXEncoding::new();
for rounding in [
EvexRoundingMode::RnSae,
EvexRoundingMode::RdSae,
EvexRoundingMode::RuSae,
EvexRoundingMode::RzSae,
] {
let cfg = enc.config_with_rounding(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::None,
EvexVectorLength::VL512,
0,
1,
2,
false,
rounding,
);
assert!(cfg.b_bit);
assert!(rounding.is_active());
}
}
#[test]
fn test_evex_encoding_no_rounding_when_none() {
let enc = X86EVEXEncoding::new();
let cfg = enc.config_with_rounding(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::None,
EvexVectorLength::VL512,
0,
1,
2,
false,
EvexRoundingMode::None,
);
assert!(!cfg.b_bit);
}
#[test]
fn test_evex_select_tuple_type_variants() {
let enc = X86EVEXEncoding::new();
for es in [1, 2, 4, 8] {
let tt = enc.select_tuple_type(es, false);
assert_eq!(tt, EvexTupleType::Full);
let tt = enc.select_tuple_type(es, true);
assert_eq!(tt, EvexTupleType::FullMem);
}
}
#[test]
fn test_evex_x86_encoding_defaults() {
let enc = X86EVEXEncoding::default();
assert_eq!(enc.default_vector_length, EvexVectorLength::VL512);
assert!(enc.prefer_compressed_disp);
assert!(enc.allow_evex_to_vex_compression);
}
#[test]
fn test_evex_x86_encoding_builder() {
let enc = X86EVEXEncoding::new()
.with_default_vl(EvexVectorLength::VL256)
.with_default_opmask(EvexOpmaskConfig::merge(2))
.with_evex_to_vex_compression(false);
assert_eq!(enc.default_vector_length, EvexVectorLength::VL256);
assert_eq!(enc.default_opmask.opmask_reg, 2);
assert!(!enc.allow_evex_to_vex_compression);
}
#[test]
fn test_evex_config_prefix_byte_count() {
let cfg = EvexPrefixConfig::new();
assert_eq!(cfg.prefix_byte_count(), 4);
}
#[test]
fn test_evex_p1_fixed_bit_present() {
let bytes = EvexPrefixBuilder::build_raw(
false, false, false, false, false, false, EVEX_MM_0F, false, false, 0, 0, false, false,
false, false, 0,
);
assert!(bytes[2] & EVEX_P1_FIXED_BIT != 0);
}
#[test]
fn test_evex_effective_vvvv_with_vprime() {
let decoded = EvexDecodedPrefix::decode(0xF1, 0x74, 0x00); assert!(decoded.v_prime);
let eff = decoded.effective_vvvv_reg();
assert!(eff <= 31);
}
#[test]
fn test_full_evex_encode_with_all_bells_and_whistles() {
let enc = X86EVEXEncoding::new();
let instr = enc.encode_broadcast(
EvexOpcodeMap::Map0F,
VexMandatoryPrefix::P66,
EvexVectorLength::VL512,
0x58, 20, 18, 14, EvexBroadcastMode::Broadcast1To16,
EvexOpmaskConfig::zero(5),
64, 4, );
assert!(instr.is_valid());
assert!(instr.broadcast.is_broadcast());
assert!(instr.opmask.has_zeroing());
assert_eq!(instr.opmask.opmask_reg, 5);
assert_eq!(instr.displacement, vec![16u8]);
}
}