use crate::x86::x86_instr_info::X86Opcode;
use crate::x86::x86_shuffle_patterns::{ShuffleClass, ShuffleMask, ShuffleMaskEncoder};
use crate::x86::x86_subtarget::X86Subtarget;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CombineKind {
ShuffleShuffle,
InsertExtractNoop,
BroadcastOp,
BlendBlend,
ConcatSimplify,
ExtractSimplify,
BitcastChain,
IdentityShuffle,
DuplicateShuffle,
ConstantFold,
HorizontalOp,
ExtractInsertMerge,
PermutePermute,
UnpackPackCancel,
ExtractExtract,
}
impl std::fmt::Display for CombineKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
CombineKind::ShuffleShuffle => "shuffle+shuffle",
CombineKind::InsertExtractNoop => "insert+extract→noop",
CombineKind::BroadcastOp => "broadcast+op",
CombineKind::BlendBlend => "blend+blend",
CombineKind::ConcatSimplify => "concat→simplify",
CombineKind::ExtractSimplify => "extract→simplify",
CombineKind::BitcastChain => "bitcast→bitcast",
CombineKind::IdentityShuffle => "identity→noop",
CombineKind::DuplicateShuffle => "duplicate shuffle elim",
CombineKind::ConstantFold => "constant fold",
CombineKind::HorizontalOp => "horizontal op",
CombineKind::ExtractInsertMerge => "extract+insert merge",
CombineKind::PermutePermute => "permute+permute",
CombineKind::UnpackPackCancel => "unpack+pack cancel",
CombineKind::ExtractExtract => "extract+extract",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CombineShuffleMask {
pub indices: Vec<u8>,
pub num_src_elements: u8,
pub num_dst_elements: u8,
pub elem_bits: u8,
}
impl CombineShuffleMask {
pub fn identity(n: u8, elem_bits: u8) -> Self {
Self {
indices: (0..n).collect(),
num_src_elements: n,
num_dst_elements: n,
elem_bits,
}
}
pub fn is_identity(&self) -> bool {
if self.num_src_elements != self.num_dst_elements {
return false;
}
self.indices
.iter()
.enumerate()
.all(|(i, &idx)| idx == i as u8)
}
pub fn is_reverse(&self) -> bool {
let n = self.num_src_elements as usize;
if n != self.num_dst_elements as usize {
return false;
}
self.indices
.iter()
.enumerate()
.all(|(i, &idx)| idx == (n - 1 - i) as u8)
}
pub fn is_broadcast(&self) -> Option<u8> {
if self.indices.is_empty() {
return None;
}
let first = self.indices[0];
if self.indices.iter().all(|&idx| idx == first) {
Some(first)
} else {
None
}
}
pub fn compose(&self, other: &CombineShuffleMask) -> Option<CombineShuffleMask> {
if self.num_src_elements != other.num_dst_elements {
return None;
}
let mut indices = Vec::with_capacity(other.indices.len());
for &idx in &other.indices {
if idx == u8::MAX {
indices.push(u8::MAX);
} else if (idx as usize) < self.indices.len() {
indices.push(self.indices[idx as usize]);
} else {
return None;
}
}
Some(CombineShuffleMask {
indices,
num_src_elements: self.num_src_elements,
num_dst_elements: self.num_dst_elements,
elem_bits: self.elem_bits.min(other.elem_bits),
})
}
pub fn to_pshufd_imm(&self) -> Option<u8> {
if self.num_dst_elements != 4 || self.elem_bits != 32 {
return None;
}
if self.indices.len() < 4 {
return None;
}
let mut imm: u8 = 0;
for (i, &idx) in self.indices.iter().take(4).enumerate() {
if idx >= 4 {
return None;
}
imm |= idx << (i * 2);
}
Some(imm)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlendMask {
pub selects: Vec<bool>,
pub num_elements: u8,
}
impl BlendMask {
pub fn all_first(n: u8) -> Self {
Self {
selects: vec![true; n as usize],
num_elements: n,
}
}
pub fn all_second(n: u8) -> Self {
Self {
selects: vec![false; n as usize],
num_elements: n,
}
}
pub fn compose(&self, other: &BlendMask) -> Option<BlendMask> {
if self.num_elements != other.num_elements {
return None;
}
let n = self.num_elements as usize;
let mut selects = Vec::with_capacity(n);
for i in 0..n {
let use_src1_from_other = other.selects[i];
let want_src1_from_self = self.selects[i];
selects.push(use_src1_from_other == want_src1_from_self);
}
Some(BlendMask {
selects,
num_elements: self.num_elements,
})
}
pub fn to_imm(&self) -> Option<u8> {
if self.num_elements > 8 {
return None;
}
let mut imm: u8 = 0;
for (i, &sel) in self.selects.iter().enumerate() {
if sel {
imm |= 1 << i;
}
}
Some(imm)
}
pub fn is_all_first(&self) -> bool {
self.selects.iter().all(|&s| s)
}
pub fn is_all_second(&self) -> bool {
self.selects.iter().all(|&s| !s)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstantVector {
pub elements: Vec<i64>,
pub num_elements: u8,
pub elem_bits: u8,
pub is_float: bool,
}
impl ConstantVector {
pub fn new(elements: Vec<i64>, elem_bits: u8, is_float: bool) -> Self {
let n = elements.len() as u8;
Self {
elements,
num_elements: n,
elem_bits,
is_float,
}
}
pub fn zero(num_elements: u8, elem_bits: u8) -> Self {
Self {
elements: vec![0; num_elements as usize],
num_elements,
elem_bits,
is_float: false,
}
}
pub fn all_ones(num_elements: u8, elem_bits: u8) -> Self {
let mask = if elem_bits >= 64 {
u64::MAX
} else {
(1u64 << elem_bits) - 1
};
Self {
elements: vec![mask as i64; num_elements as usize],
num_elements,
elem_bits,
is_float: false,
}
}
pub fn add(&self, other: &ConstantVector) -> Option<ConstantVector> {
if self.num_elements != other.num_elements || self.elem_bits != other.elem_bits {
return None;
}
let elements: Vec<i64> = self
.elements
.iter()
.zip(&other.elements)
.map(|(&a, &b)| a.wrapping_add(*b))
.collect();
Some(ConstantVector::new(elements, self.elem_bits, self.is_float))
}
pub fn sub(&self, other: &ConstantVector) -> Option<ConstantVector> {
if self.num_elements != other.num_elements || self.elem_bits != other.elem_bits {
return None;
}
let elements: Vec<i64> = self
.elements
.iter()
.zip(&other.elements)
.map(|(&a, &b)| a.wrapping_sub(*b))
.collect();
Some(ConstantVector::new(elements, self.elem_bits, self.is_float))
}
pub fn mul(&self, other: &ConstantVector) -> Option<ConstantVector> {
if self.num_elements != other.num_elements || self.elem_bits != other.elem_bits {
return None;
}
let elements: Vec<i64> = self
.elements
.iter()
.zip(&other.elements)
.map(|(&a, &b)| a.wrapping_mul(*b))
.collect();
Some(ConstantVector::new(elements, self.elem_bits, self.is_float))
}
pub fn and(&self, other: &ConstantVector) -> Option<ConstantVector> {
if self.num_elements != other.num_elements || self.elem_bits != other.elem_bits {
return None;
}
let elements: Vec<i64> = self
.elements
.iter()
.zip(&other.elements)
.map(|(&a, &b)| a & b)
.collect();
Some(ConstantVector::new(elements, self.elem_bits, self.is_float))
}
pub fn or(&self, other: &ConstantVector) -> Option<ConstantVector> {
if self.num_elements != other.num_elements || self.elem_bits != other.elem_bits {
return None;
}
let elements: Vec<i64> = self
.elements
.iter()
.zip(&other.elements)
.map(|(&a, &b)| a | b)
.collect();
Some(ConstantVector::new(elements, self.elem_bits, self.is_float))
}
pub fn xor(&self, other: &ConstantVector) -> Option<ConstantVector> {
if self.num_elements != other.num_elements || self.elem_bits != other.elem_bits {
return None;
}
let elements: Vec<i64> = self
.elements
.iter()
.zip(&other.elements)
.map(|(&a, &b)| a ^ b)
.collect();
Some(ConstantVector::new(elements, self.elem_bits, self.is_float))
}
pub fn is_zero(&self) -> bool {
self.elements.iter().all(|&e| e == 0)
}
pub fn is_all_ones(&self) -> bool {
let mask = if self.elem_bits >= 64 {
u64::MAX as i64
} else {
((1u64 << self.elem_bits) - 1) as i64
};
self.elements.iter().all(|&e| e == mask)
}
}
#[derive(Debug, Clone)]
pub struct CombineResult {
pub kind: CombineKind,
pub original_opcodes: Vec<X86Opcode>,
pub combined_opcode: X86Opcode,
pub description: String,
pub instructions_eliminated: u32,
pub new_shuffle_mask: Option<CombineShuffleMask>,
pub new_blend_mask: Option<BlendMask>,
pub constant_result: Option<ConstantVector>,
}
#[derive(Debug, Clone, Default)]
pub struct CombineStats {
pub shuffle_shuffle: u32,
pub insert_extract_noop: u32,
pub broadcast_op: u32,
pub blend_blend: u32,
pub concat_simplify: u32,
pub extract_simplify: u32,
pub bitcast_chain: u32,
pub identity_shuffle: u32,
pub duplicate_shuffle: u32,
pub constant_fold: u32,
pub horizontal_op: u32,
pub extract_insert_merge: u32,
pub permute_permute: u32,
pub unpack_pack_cancel: u32,
pub total_eliminated: u32,
pub candidates_examined: u32,
}
impl CombineStats {
pub fn merge(&mut self, other: &CombineStats) {
self.shuffle_shuffle += other.shuffle_shuffle;
self.insert_extract_noop += other.insert_extract_noop;
self.broadcast_op += other.broadcast_op;
self.blend_blend += other.blend_blend;
self.concat_simplify += other.concat_simplify;
self.extract_simplify += other.extract_simplify;
self.bitcast_chain += other.bitcast_chain;
self.identity_shuffle += other.identity_shuffle;
self.duplicate_shuffle += other.duplicate_shuffle;
self.constant_fold += other.constant_fold;
self.horizontal_op += other.horizontal_op;
self.extract_insert_merge += other.extract_insert_merge;
self.permute_permute += other.permute_permute;
self.unpack_pack_cancel += other.unpack_pack_cancel;
self.total_eliminated += other.total_eliminated;
self.candidates_examined += other.candidates_examined;
}
pub fn made_progress(&self) -> bool {
self.total_eliminated > 0
}
pub fn summary(&self) -> String {
format!(
"X86VectorCombine: {} shuffle+shuffle, {} insert+extract, {} broadcast+op, \
{} blend+blend, {} concat, {} extract, {} bitcast, {} identity, \
{} dups, {} const, {} horiz, {} merge, {} permute, {} unpack → {} eliminated",
self.shuffle_shuffle,
self.insert_extract_noop,
self.broadcast_op,
self.blend_blend,
self.concat_simplify,
self.extract_simplify,
self.bitcast_chain,
self.identity_shuffle,
self.duplicate_shuffle,
self.constant_fold,
self.horizontal_op,
self.extract_insert_merge,
self.permute_permute,
self.unpack_pack_cancel,
self.total_eliminated,
)
}
}
pub struct X86VectorCombine {
pub subtarget: X86Subtarget,
pub stats: CombineStats,
pub enable_shuffle_compose: bool,
pub enable_insert_extract: bool,
pub enable_broadcast_op: bool,
pub enable_blend_compose: bool,
pub enable_identity_shuffle: bool,
pub enable_constant_fold: bool,
pub enable_horizontal: bool,
pub enable_duplicate_shuffle: bool,
pub enable_bitcast_chain: bool,
pub max_combines_per_block: usize,
pub max_iterations: usize,
}
impl X86VectorCombine {
pub fn new(subtarget: X86Subtarget) -> Self {
Self {
subtarget,
stats: CombineStats::default(),
enable_shuffle_compose: true,
enable_insert_extract: true,
enable_broadcast_op: true,
enable_blend_compose: true,
enable_identity_shuffle: true,
enable_constant_fold: true,
enable_horizontal: true,
enable_duplicate_shuffle: true,
enable_bitcast_chain: true,
max_combines_per_block: 128,
max_iterations: 4,
}
}
pub fn new_aggressive(subtarget: X86Subtarget) -> Self {
let mut c = Self::new(subtarget);
c.max_iterations = 8;
c.max_combines_per_block = 256;
c
}
pub fn run_on_block(&mut self, instructions: &[MachineCombineInstr]) -> Vec<CombineResult> {
let mut results = Vec::new();
for _iteration in 0..self.max_iterations {
let before_count = results.len();
let mut block_results = self.combine_block(instructions, &results);
if block_results.is_empty() {
break;
}
let new_count = block_results.len();
results.append(&mut block_results);
if new_count == 0 || results.len() == before_count {
break;
}
}
self.stats.total_eliminated += results
.iter()
.map(|r| r.instructions_eliminated)
.sum::<u32>();
results
}
fn combine_block(
&mut self,
instructions: &[MachineCombineInstr],
_existing: &[CombineResult],
) -> Vec<CombineResult> {
let mut results = Vec::new();
for (i, mi) in instructions.iter().enumerate() {
self.stats.candidates_examined += 1;
if results.len() >= self.max_combines_per_block {
break;
}
if let Some(result) = self.try_duplicate_shuffle(mi, i, instructions) {
results.push(result);
continue;
}
if let Some(result) = self.try_identity_shuffle(mi) {
results.push(result);
continue;
}
if let Some(result) = self.try_insert_extract_noop(mi, i, instructions) {
results.push(result);
continue;
}
if let Some(result) = self.try_shuffle_compose(mi, i, instructions) {
results.push(result);
continue;
}
if let Some(result) = self.try_blend_compose(mi, i, instructions) {
results.push(result);
continue;
}
if let Some(result) = self.try_bitcast_chain(mi, i, instructions) {
results.push(result);
continue;
}
if let Some(result) = self.try_constant_fold(mi) {
results.push(result);
continue;
}
if let Some(result) = self.try_horizontal_op(mi, i, instructions) {
results.push(result);
continue;
}
if let Some(result) = self.try_extract_insert_merge(mi, i, instructions) {
results.push(result);
continue;
}
}
results
}
fn try_duplicate_shuffle(
&mut self,
mi: &MachineCombineInstr,
idx: usize,
instrs: &[MachineCombineInstr],
) -> Option<CombineResult> {
if !self.enable_duplicate_shuffle {
return None;
}
if !mi.is_shuffle() {
return None;
}
if idx + 1 >= instrs.len() {
return None;
}
let next = &instrs[idx + 1];
if !next.is_shuffle() {
return None;
}
if mi.opcode != next.opcode {
return None;
}
let imm1 = mi.imm_op(2);
let imm2 = next.imm_op(2);
if imm1 != imm2 {
return None;
}
if mi.dst_reg() != next.src_reg(0) {
return None;
}
self.stats.duplicate_shuffle += 1;
Some(CombineResult {
kind: CombineKind::DuplicateShuffle,
original_opcodes: vec![mi.opcode, next.opcode],
combined_opcode: mi.opcode,
description: "eliminated duplicate shuffle".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
})
}
fn try_identity_shuffle(&mut self, mi: &MachineCombineInstr) -> Option<CombineResult> {
if !self.enable_identity_shuffle {
return None;
}
if !mi.is_shuffle() {
return None;
}
let mask = mi.get_shuffle_mask()?;
if !mask.is_identity() {
return None;
}
self.stats.identity_shuffle += 1;
Some(CombineResult {
kind: CombineKind::IdentityShuffle,
original_opcodes: vec![mi.opcode],
combined_opcode: mi.opcode,
description: "identity shuffle eliminated".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
})
}
fn try_insert_extract_noop(
&mut self,
mi: &MachineCombineInstr,
idx: usize,
instrs: &[MachineCombineInstr],
) -> Option<CombineResult> {
if !self.enable_insert_extract {
return None;
}
if !mi.is_insert_subvector() {
return None;
}
if idx + 1 >= instrs.len() {
return None;
}
let next = &instrs[idx + 1];
if !next.is_extract_subvector() {
return None;
}
let insert_idx = mi.imm_op(2)?;
let extract_idx = next.imm_op(1)?;
if insert_idx != extract_idx {
return None;
}
if mi.dst_reg() != next.src_reg(0) {
return None;
}
self.stats.insert_extract_noop += 1;
Some(CombineResult {
kind: CombineKind::InsertExtractNoop,
original_opcodes: vec![mi.opcode, next.opcode],
combined_opcode: next.opcode,
description: "insert+extract same index eliminated".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
})
}
fn try_shuffle_compose(
&mut self,
mi: &MachineCombineInstr,
idx: usize,
instrs: &[MachineCombineInstr],
) -> Option<CombineResult> {
if !self.enable_shuffle_compose {
return None;
}
if !mi.is_shuffle() {
return None;
}
if idx + 1 >= instrs.len() {
return None;
}
let next = &instrs[idx + 1];
if !next.is_shuffle() {
return None;
}
if mi.dst_reg() != next.src_reg(0) {
return None;
}
let mask1 = mi.get_shuffle_mask()?;
let mask2 = next.get_shuffle_mask()?;
let composed = mask1.compose(&mask2)?;
self.stats.shuffle_shuffle += 1;
Some(CombineResult {
kind: CombineKind::ShuffleShuffle,
original_opcodes: vec![mi.opcode, next.opcode],
combined_opcode: mi.opcode,
description: format!(
"composed two shuffles → single shuffle ({}→{})",
composed.num_src_elements, composed.num_dst_elements
),
instructions_eliminated: 1,
new_shuffle_mask: Some(composed),
new_blend_mask: None,
constant_result: None,
})
}
fn try_blend_compose(
&mut self,
mi: &MachineCombineInstr,
idx: usize,
instrs: &[MachineCombineInstr],
) -> Option<CombineResult> {
if !self.enable_blend_compose {
return None;
}
if !mi.is_blend() {
return None;
}
if idx + 1 >= instrs.len() {
return None;
}
let next = &instrs[idx + 1];
if !next.is_blend() {
return None;
}
if mi.dst_reg() != next.src_reg(0) {
return None;
}
let blend1 = mi.get_blend_mask()?;
let blend2 = next.get_blend_mask()?;
let composed = blend1.compose(&blend2)?;
self.stats.blend_blend += 1;
Some(CombineResult {
kind: CombineKind::BlendBlend,
original_opcodes: vec![mi.opcode, next.opcode],
combined_opcode: mi.opcode,
description: "composed two blends → single blend".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: Some(composed),
constant_result: None,
})
}
fn try_bitcast_chain(
&mut self,
mi: &MachineCombineInstr,
idx: usize,
instrs: &[MachineCombineInstr],
) -> Option<CombineResult> {
if !self.enable_bitcast_chain {
return None;
}
if !mi.is_bitcast() {
return None;
}
if idx + 1 >= instrs.len() {
return None;
}
let next = &instrs[idx + 1];
if !next.is_bitcast() {
return None;
}
if mi.dst_reg() != next.src_reg(0) {
return None;
}
self.stats.bitcast_chain += 1;
Some(CombineResult {
kind: CombineKind::BitcastChain,
original_opcodes: vec![mi.opcode, next.opcode],
combined_opcode: next.opcode,
description: "collapsed bitcast chain".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
})
}
fn try_constant_fold(&mut self, mi: &MachineCombineInstr) -> Option<CombineResult> {
if !self.enable_constant_fold {
return None;
}
let lhs = mi.get_constant_operand(0)?;
let rhs = mi.get_constant_operand(1)?;
let result = match mi.opcode {
X86Opcode::PADDB | X86Opcode::PADDW | X86Opcode::PADDD | X86Opcode::PADDQ => {
lhs.add(&rhs)
}
X86Opcode::PSUBB | X86Opcode::PSUBW | X86Opcode::PSUBD | X86Opcode::PSUBQ => {
lhs.sub(&rhs)
}
X86Opcode::PMULLW | X86Opcode::PMULLD => lhs.mul(&rhs),
X86Opcode::PAND => lhs.and(&rhs),
X86Opcode::POR => lhs.or(&rhs),
X86Opcode::PXOR => lhs.xor(&rhs),
_ => return None,
};
let result = result?;
self.stats.constant_fold += 1;
Some(CombineResult {
kind: CombineKind::ConstantFold,
original_opcodes: vec![mi.opcode],
combined_opcode: mi.opcode,
description: format!(
"folded constant vector op ({} elements x {} bits)",
result.num_elements, result.elem_bits
),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: Some(result),
})
}
fn try_horizontal_op(
&mut self,
mi: &MachineCombineInstr,
_idx: usize,
_instrs: &[MachineCombineInstr],
) -> Option<CombineResult> {
if !self.enable_horizontal {
return None;
}
let is_add = matches!(mi.opcode, X86Opcode::ADDPS | X86Opcode::ADDPD);
let is_sub = matches!(mi.opcode, X86Opcode::SUBPS | X86Opcode::SUBPD);
if !is_add && !is_sub {
return None;
}
let _src1 = mi.src_reg(0)?;
let src2 = mi.src_reg(1)?;
let horizontal_opcode = if is_add {
X86Opcode::HADDPS
} else {
X86Opcode::HSUBPS
};
self.stats.horizontal_op += 1;
Some(CombineResult {
kind: CombineKind::HorizontalOp,
original_opcodes: vec![mi.opcode],
combined_opcode: horizontal_opcode,
description: "recognized horizontal op pattern".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
})
}
fn try_extract_insert_merge(
&mut self,
mi: &MachineCombineInstr,
idx: usize,
instrs: &[MachineCombineInstr],
) -> Option<CombineResult> {
if !self.enable_insert_extract {
return None;
}
if !mi.is_extract_subvector() {
return None;
}
if idx + 1 >= instrs.len() {
return None;
}
let next = &instrs[idx + 1];
if !next.is_insert_subvector() {
return None;
}
if mi.dst_reg() != next.src_reg(1) {
return None;
}
self.stats.extract_insert_merge += 1;
Some(CombineResult {
kind: CombineKind::ExtractInsertMerge,
original_opcodes: vec![mi.opcode, next.opcode],
combined_opcode: next.opcode,
description: "merged extract+insert".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
})
}
pub fn clear(&mut self) {
self.stats = CombineStats::default();
}
}
impl Default for X86VectorCombine {
fn default() -> Self {
Self::new(X86Subtarget::default())
}
}
#[derive(Debug, Clone)]
pub struct MachineCombineInstr {
pub opcode: X86Opcode,
pub operands: Vec<CombineOperand>,
pub def_reg: Option<u32>,
pub vec_width: u32,
pub constant_data: Option<ConstantVector>,
}
impl MachineCombineInstr {
pub fn new(opcode: X86Opcode, vec_width: u32) -> Self {
Self {
opcode,
operands: Vec::new(),
def_reg: None,
vec_width,
constant_data: None,
}
}
pub fn src_reg(&self, idx: usize) -> Option<u32> {
self.operands.get(idx).and_then(|op| match op {
CombineOperand::Reg(r) => Some(*r),
_ => None,
})
}
pub fn dst_reg(&self) -> Option<u32> {
self.def_reg
}
pub fn imm_op(&self, idx: usize) -> Option<i64> {
self.operands.get(idx).and_then(|op| match op {
CombineOperand::Imm(v) => Some(*v),
_ => None,
})
}
pub fn is_shuffle(&self) -> bool {
let op = self.opcode as u32;
let base = X86Opcode::PSHUFD as u32;
let end = X86Opcode::VPERM2F128 as u32;
(op >= base && op <= end)
|| self.opcode == X86Opcode::SHUFPS
|| self.opcode == X86Opcode::SHUFPD
|| self.opcode == X86Opcode::VSHUFPS
|| self.opcode == X86Opcode::VSHUFPD
}
pub fn is_blend(&self) -> bool {
matches!(
self.opcode,
X86Opcode::BLENDPS
| X86Opcode::BLENDPD
| X86Opcode::PBLENDW
| X86Opcode::VBLENDPS
| X86Opcode::VBLENDPD
| X86Opcode::VPBLENDW
)
}
pub fn is_bitcast(&self) -> bool {
self.opcode == X86Opcode::BITCAST
}
pub fn is_insert_subvector(&self) -> bool {
matches!(
self.opcode,
X86Opcode::INSERT_SUBVEC | X86Opcode::VINSERTF128 | X86Opcode::VINSERTI128
)
}
pub fn is_extract_subvector(&self) -> bool {
matches!(
self.opcode,
X86Opcode::EXTRACT_SUBVEC | X86Opcode::VEXTRACTF128 | X86Opcode::VEXTRACTI128
)
}
pub fn get_shuffle_mask(&self) -> Option<CombineShuffleMask> {
match self.opcode {
X86Opcode::PSHUFD => {
let imm = self.imm_op(2)? as u8;
let encoder = ShuffleMaskEncoder;
let indices = encoder.decode_pshufd(imm);
Some(CombineShuffleMask {
indices: indices.to_vec(),
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
})
}
X86Opcode::SHUFPS => {
let imm = self.imm_op(2)? as u8;
let indices = vec![
imm & 0x3,
(imm >> 2) & 0x3,
(imm >> 4) & 0x3,
(imm >> 6) & 0x3,
];
Some(CombineShuffleMask {
indices,
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
})
}
_ => {
if self.is_shuffle() {
let imm = self.imm_op(2)? as u8;
let n = self.vec_width / 32; let indices: Vec<u8> = (0..n as u8).map(|i| (imm >> (i * 2)) & 0x3).collect();
Some(CombineShuffleMask {
indices,
num_src_elements: n as u8,
num_dst_elements: n as u8,
elem_bits: 32,
})
} else {
None
}
}
}
}
pub fn get_blend_mask(&self) -> Option<BlendMask> {
if !self.is_blend() {
return None;
}
let imm = self.imm_op(2)? as u8;
let n = match self.opcode {
X86Opcode::PBLENDW | X86Opcode::VPBLENDW => 8,
_ => {
if self.opcode == X86Opcode::BLENDPD || self.opcode == X86Opcode::VBLENDPD {
self.vec_width / 64
} else {
self.vec_width / 32
}
}
};
let selects: Vec<bool> = (0..n).map(|i| (imm >> i) & 1 != 0).collect();
Some(BlendMask {
selects,
num_elements: n as u8,
})
}
pub fn get_constant_operand(&self, idx: usize) -> Option<ConstantVector> {
if idx == 0 {
self.constant_data.clone()
} else {
self.constant_data.clone()
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CombineOperand {
Reg(u32),
Imm(i64),
Mem(u64),
SubvecIndex(u8),
}
#[derive(Debug, Clone)]
pub struct CombineEmitter {
pub sequence: Vec<CombineEmittedInstr>,
next_temp: u32,
}
impl CombineEmitter {
pub fn new() -> Self {
Self {
sequence: Vec::new(),
next_temp: 100,
}
}
pub fn emit_combine(&mut self, result: &CombineResult) {
match result.kind {
CombineKind::IdentityShuffle | CombineKind::DuplicateShuffle => {
}
CombineKind::ShuffleShuffle => {
if let Some(ref mask) = result.new_shuffle_mask {
self.emit_shuffle(result.combined_opcode as u32, mask);
}
}
CombineKind::BlendBlend => {
if let Some(ref mask) = result.new_blend_mask {
self.emit_blend(result.combined_opcode as u32, mask);
}
}
CombineKind::ConstantFold => {
if let Some(ref cv) = result.constant_result {
self.emit_constant_load(cv);
}
}
CombineKind::HorizontalOp => {
self.sequence.push(CombineEmittedInstr {
opcode: result.combined_opcode as u32,
def_reg: 0,
use_regs: vec![1, 2],
imm: None,
comment: "horizontal op".into(),
});
}
_ => {
self.sequence.push(CombineEmittedInstr {
opcode: result.combined_opcode as u32,
def_reg: 0,
use_regs: vec![],
imm: None,
comment: result.description.clone(),
});
}
}
}
fn emit_shuffle(&mut self, opcode: u32, mask: &CombineShuffleMask) {
let imm = mask.to_pshufd_imm();
self.sequence.push(CombineEmittedInstr {
opcode,
def_reg: 0,
use_regs: vec![1],
imm: imm.map(|i| i as i64),
comment: format!("composed shuffle: {} elems", mask.num_dst_elements),
});
}
fn emit_blend(&mut self, opcode: u32, mask: &BlendMask) {
let imm = mask.to_imm();
self.sequence.push(CombineEmittedInstr {
opcode,
def_reg: 0,
use_regs: vec![1, 2],
imm: imm.map(|i| i as i64),
comment: format!("composed blend: {} elems", mask.num_elements),
});
}
fn emit_constant_load(&mut self, cv: &ConstantVector) {
self.sequence.push(CombineEmittedInstr {
opcode: X86Opcode::MOVDQA as u32,
def_reg: 0,
use_regs: vec![],
imm: Some(cv.elements[0]),
comment: format!(
"folded constant: {} elements x {} bits",
cv.num_elements, cv.elem_bits
),
});
}
pub fn alloc_temp(&mut self) -> u32 {
let t = self.next_temp;
self.next_temp += 1;
t
}
}
impl Default for CombineEmitter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CombineEmittedInstr {
pub opcode: u32,
pub def_reg: u32,
pub use_regs: Vec<u32>,
pub imm: Option<i64>,
pub comment: String,
}
pub fn make_x86_vector_combine(subtarget: X86Subtarget) -> X86VectorCombine {
X86VectorCombine::new(subtarget)
}
pub fn make_x86_vector_combine_aggressive(subtarget: X86Subtarget) -> X86VectorCombine {
X86VectorCombine::new_aggressive(subtarget)
}
pub fn make_x86_vector_combine_conservative(subtarget: X86Subtarget) -> X86VectorCombine {
let mut c = X86VectorCombine::new(subtarget);
c.enable_constant_fold = false;
c.enable_horizontal = false;
c
}
#[cfg(test)]
mod tests {
use super::*;
fn make_subtarget() -> X86Subtarget {
X86Subtarget::default()
}
fn make_mi(opcode: X86Opcode, vec_width: u32, def: u32, srcs: &[u32]) -> MachineCombineInstr {
let mut mi = MachineCombineInstr::new(opcode, vec_width);
mi.def_reg = Some(def);
for &s in srcs {
mi.operands.push(CombineOperand::Reg(s));
}
mi
}
fn make_mi_with_imm(
opcode: X86Opcode,
vec_width: u32,
def: u32,
srcs: &[u32],
imm: i64,
) -> MachineCombineInstr {
let mut mi = make_mi(opcode, vec_width, def, srcs);
mi.operands.push(CombineOperand::Imm(imm));
mi
}
fn make_shuffle_mi(
opcode: X86Opcode,
vec_width: u32,
def: u32,
src: u32,
imm: i64,
) -> MachineCombineInstr {
let mut mi = MachineCombineInstr::new(opcode, vec_width);
mi.def_reg = Some(def);
mi.operands.push(CombineOperand::Reg(src));
mi.operands.push(CombineOperand::Reg(src)); mi.operands.push(CombineOperand::Imm(imm));
mi
}
#[test]
fn test_combine_shuffle_mask_identity() {
let mask = CombineShuffleMask::identity(4, 32);
assert!(mask.is_identity());
assert!(!mask.is_reverse());
assert_eq!(mask.to_pshufd_imm(), Some(0xE4)); }
#[test]
fn test_combine_shuffle_mask_reverse() {
let mask = CombineShuffleMask {
indices: vec![3, 2, 1, 0],
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
};
assert!(mask.is_reverse());
assert!(!mask.is_identity());
assert_eq!(mask.to_pshufd_imm(), Some(0x1B)); }
#[test]
fn test_combine_shuffle_mask_broadcast() {
let mask = CombineShuffleMask {
indices: vec![2, 2, 2, 2],
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
};
assert_eq!(mask.is_broadcast(), Some(2));
}
#[test]
fn test_combine_shuffle_mask_not_broadcast() {
let mask = CombineShuffleMask::identity(4, 32);
assert_eq!(mask.is_broadcast(), None);
}
#[test]
fn test_combine_shuffle_mask_compose() {
let mask1 = CombineShuffleMask {
indices: vec![3, 2, 1, 0],
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
};
let mask2 = CombineShuffleMask {
indices: vec![3, 2, 1, 0],
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
};
let composed = mask1.compose(&mask2).unwrap();
assert!(composed.is_identity());
}
#[test]
fn test_combine_shuffle_mask_compose_different_sizes() {
let mask1 = CombineShuffleMask {
indices: vec![0, 1],
num_src_elements: 4,
num_dst_elements: 2,
elem_bits: 32,
};
let mask2 = CombineShuffleMask {
indices: vec![3, 2],
num_src_elements: 2,
num_dst_elements: 2,
elem_bits: 32,
};
assert!(mask1.compose(&mask2).is_none());
}
#[test]
fn test_combine_shuffle_mask_to_pshufd_imm_wrong_elem_count() {
let mask = CombineShuffleMask {
indices: vec![0, 1],
num_src_elements: 2,
num_dst_elements: 2,
elem_bits: 32,
};
assert_eq!(mask.to_pshufd_imm(), None); }
#[test]
fn test_blend_mask_all_first() {
let mask = BlendMask::all_first(4);
assert!(mask.is_all_first());
assert!(!mask.is_all_second());
}
#[test]
fn test_blend_mask_all_second() {
let mask = BlendMask::all_second(4);
assert!(mask.is_all_second());
assert!(!mask.is_all_first());
}
#[test]
fn test_blend_mask_compose() {
let blend1 = BlendMask::all_second(4);
let blend2 = BlendMask::all_first(4);
let composed = blend1.compose(&blend2).unwrap();
assert!(composed.is_all_second());
}
#[test]
fn test_blend_mask_compose_different_sizes() {
let blend1 = BlendMask::all_first(4);
let blend2 = BlendMask::all_first(8);
assert!(blend1.compose(&blend2).is_none());
}
#[test]
fn test_blend_mask_to_imm() {
let mask = BlendMask {
selects: vec![true, false, true, false],
num_elements: 4,
};
assert_eq!(mask.to_imm(), Some(0b0101));
}
#[test]
fn test_blend_mask_to_imm_too_many_elements() {
let mask = BlendMask {
selects: vec![true; 16],
num_elements: 16,
};
assert_eq!(mask.to_imm(), None);
}
#[test]
fn test_constant_vector_zero() {
let cv = ConstantVector::zero(4, 32);
assert!(cv.is_zero());
assert!(!cv.is_all_ones());
}
#[test]
fn test_constant_vector_all_ones() {
let cv = ConstantVector::all_ones(4, 32);
assert!(cv.is_all_ones());
assert!(!cv.is_zero());
}
#[test]
fn test_constant_vector_add() {
let a = ConstantVector::new(vec![1, 2, 3, 4], 32, false);
let b = ConstantVector::new(vec![5, 6, 7, 8], 32, false);
let sum = a.add(&b).unwrap();
assert_eq!(sum.elements, vec![6, 8, 10, 12]);
}
#[test]
fn test_constant_vector_add_different_sizes() {
let a = ConstantVector::new(vec![1, 2], 32, false);
let b = ConstantVector::new(vec![1, 2, 3], 32, false);
assert!(a.add(&b).is_none());
}
#[test]
fn test_constant_vector_sub() {
let a = ConstantVector::new(vec![10, 20, 30, 40], 32, false);
let b = ConstantVector::new(vec![1, 2, 3, 4], 32, false);
let diff = a.sub(&b).unwrap();
assert_eq!(diff.elements, vec![9, 18, 27, 36]);
}
#[test]
fn test_constant_vector_mul() {
let a = ConstantVector::new(vec![2, 3, 4, 5], 32, false);
let b = ConstantVector::new(vec![3, 4, 5, 6], 32, false);
let prod = a.mul(&b).unwrap();
assert_eq!(prod.elements, vec![6, 12, 20, 30]);
}
#[test]
fn test_constant_vector_and() {
let a = ConstantVector::new(vec![0xFF, 0x0F, 0xF0, 0xAA], 8, false);
let b = ConstantVector::new(vec![0x0F, 0xFF, 0x0F, 0x55], 8, false);
let result = a.and(&b).unwrap();
assert_eq!(result.elements, vec![0x0F, 0x0F, 0x00, 0x00]);
}
#[test]
fn test_constant_vector_or() {
let a = ConstantVector::new(vec![0xF0, 0x0F, 0x00, 0xAA], 8, false);
let b = ConstantVector::new(vec![0x0F, 0xF0, 0xFF, 0x55], 8, false);
let result = a.or(&b).unwrap();
assert_eq!(result.elements, vec![-1i64 as u8 as i64, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn test_constant_vector_xor() {
let a = ConstantVector::new(vec![0xFF, 0x0F, 0xF0, 0xAA], 8, false);
let b = ConstantVector::new(vec![0x0F, 0x0F, 0xF0, 0x55], 8, false);
let result = a.xor(&b).unwrap();
assert_eq!(result.elements, vec![0xF0, 0x00, 0x00, 0xFF]);
}
#[test]
fn test_combine_result_construction() {
let result = CombineResult {
kind: CombineKind::ShuffleShuffle,
original_opcodes: vec![X86Opcode::PSHUFD, X86Opcode::PSHUFD],
combined_opcode: X86Opcode::PSHUFD,
description: "test".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
};
assert_eq!(result.kind, CombineKind::ShuffleShuffle);
assert_eq!(result.instructions_eliminated, 1);
}
#[test]
fn test_combine_stats_default() {
let s = CombineStats::default();
assert_eq!(s.shuffle_shuffle, 0);
assert_eq!(s.total_eliminated, 0);
assert!(!s.made_progress());
}
#[test]
fn test_combine_stats_made_progress() {
let mut s = CombineStats::default();
assert!(!s.made_progress());
s.total_eliminated = 5;
assert!(s.made_progress());
}
#[test]
fn test_combine_stats_merge() {
let mut a = CombineStats::default();
a.shuffle_shuffle = 3;
a.constant_fold = 2;
let mut b = CombineStats::default();
b.blend_blend = 1;
b.shuffle_shuffle = 1;
a.merge(&b);
assert_eq!(a.shuffle_shuffle, 4);
assert_eq!(a.blend_blend, 1);
assert_eq!(a.constant_fold, 2);
}
#[test]
fn test_combine_stats_summary() {
let mut s = CombineStats::default();
s.shuffle_shuffle = 5;
s.constant_fold = 3;
s.total_eliminated = 8;
let summary = s.summary();
assert!(summary.contains("5 shuffle+shuffle"));
assert!(summary.contains("3 const"));
assert!(summary.contains("8 eliminated"));
}
#[test]
fn test_x86_vector_combine_new() {
let st = make_subtarget();
let c = X86VectorCombine::new(st);
assert!(c.enable_shuffle_compose);
assert!(c.enable_insert_extract);
assert!(c.enable_constant_fold);
assert_eq!(c.max_iterations, 4);
assert_eq!(c.max_combines_per_block, 128);
}
#[test]
fn test_x86_vector_combine_new_aggressive() {
let st = make_subtarget();
let c = X86VectorCombine::new_aggressive(st);
assert_eq!(c.max_iterations, 8);
assert_eq!(c.max_combines_per_block, 256);
}
#[test]
fn test_x86_vector_combine_default() {
let c = X86VectorCombine::default();
assert!(c.enable_shuffle_compose);
}
#[test]
fn test_make_x86_vector_combine() {
let st = make_subtarget();
let c = make_x86_vector_combine(st);
assert!(c.enable_shuffle_compose);
}
#[test]
fn test_make_x86_vector_combine_aggressive() {
let st = make_subtarget();
let c = make_x86_vector_combine_aggressive(st);
assert_eq!(c.max_iterations, 8);
}
#[test]
fn test_make_x86_vector_combine_conservative() {
let st = make_subtarget();
let c = make_x86_vector_combine_conservative(st);
assert!(!c.enable_constant_fold);
assert!(!c.enable_horizontal);
}
#[test]
fn test_identity_shuffle_eliminated() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
let result = c.try_identity_shuffle(&mi);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, CombineKind::IdentityShuffle);
}
#[test]
fn test_identity_shuffle_not_eliminated() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0x1B);
let result = c.try_identity_shuffle(&mi);
assert!(result.is_none());
}
#[test]
fn test_identity_shuffle_disabled() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.enable_identity_shuffle = false;
let mi = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
assert!(c.try_identity_shuffle(&mi).is_none());
}
#[test]
fn test_shuffle_compose_two_reverses() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi1 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0x1B);
let mut mi2 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 3, 1, 0x1B);
mi2.operands[0] = CombineOperand::Reg(1);
let instrs = vec![mi1, mi2];
let result = c.try_shuffle_compose(&instrs[0], 0, &instrs);
assert!(result.is_some());
let r = result.unwrap();
if let Some(mask) = r.new_shuffle_mask {
assert!(mask.is_identity());
}
}
#[test]
fn test_shuffle_compose_not_connected() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi1 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0x1B);
let mi2 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 3, 4, 0x1B);
let instrs = vec![mi1, mi2];
let result = c.try_shuffle_compose(&instrs[0], 0, &instrs);
assert!(result.is_none());
}
#[test]
fn test_duplicate_shuffle_eliminated() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi1 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xAA);
let mut mi2 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 3, 1, 0xAA);
mi2.operands[0] = CombineOperand::Reg(1);
let instrs = vec![mi1, mi2];
let result = c.try_duplicate_shuffle(&instrs[0], 0, &instrs);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, CombineKind::DuplicateShuffle);
}
#[test]
fn test_duplicate_shuffle_different_masks() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi1 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xAA);
let mut mi2 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 3, 1, 0x55);
mi2.operands[0] = CombineOperand::Reg(1);
let instrs = vec![mi1, mi2];
let result = c.try_duplicate_shuffle(&instrs[0], 0, &instrs);
assert!(result.is_none());
}
#[test]
fn test_insert_extract_noop() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::INSERT_SUBVEC, 256);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
mi1.operands.push(CombineOperand::Reg(3));
mi1.operands.push(CombineOperand::Imm(1));
let mut mi2 = MachineCombineInstr::new(X86Opcode::EXTRACT_SUBVEC, 256);
mi2.def_reg = Some(4);
mi2.operands.push(CombineOperand::Reg(1)); mi2.operands.push(CombineOperand::Imm(1));
let instrs = vec![mi1, mi2];
let result = c.try_insert_extract_noop(&instrs[0], 0, &instrs);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, CombineKind::InsertExtractNoop);
}
#[test]
fn test_insert_extract_noop_different_index() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::INSERT_SUBVEC, 256);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
mi1.operands.push(CombineOperand::Reg(3));
mi1.operands.push(CombineOperand::Imm(1));
let mut mi2 = MachineCombineInstr::new(X86Opcode::EXTRACT_SUBVEC, 256);
mi2.def_reg = Some(4);
mi2.operands.push(CombineOperand::Reg(1));
mi2.operands.push(CombineOperand::Imm(2));
let instrs = vec![mi1, mi2];
let result = c.try_insert_extract_noop(&instrs[0], 0, &instrs);
assert!(result.is_none());
}
#[test]
fn test_blend_compose() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::BLENDPS, 128);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
mi1.operands.push(CombineOperand::Reg(3));
mi1.operands.push(CombineOperand::Imm(0b1010));
let mut mi2 = MachineCombineInstr::new(X86Opcode::BLENDPS, 128);
mi2.def_reg = Some(4);
mi2.operands.push(CombineOperand::Reg(1)); mi2.operands.push(CombineOperand::Reg(5));
mi2.operands.push(CombineOperand::Imm(0b1100));
let instrs = vec![mi1, mi2];
let result = c.try_blend_compose(&instrs[0], 0, &instrs);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, CombineKind::BlendBlend);
}
#[test]
fn test_bitcast_chain() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::BITCAST, 128);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
let mut mi2 = MachineCombineInstr::new(X86Opcode::BITCAST, 128);
mi2.def_reg = Some(3);
mi2.operands.push(CombineOperand::Reg(1));
let instrs = vec![mi1, mi2];
let result = c.try_bitcast_chain(&instrs[0], 0, &instrs);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, CombineKind::BitcastChain);
}
#[test]
fn test_bitcast_chain_not_connected() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::BITCAST, 128);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
let mut mi2 = MachineCombineInstr::new(X86Opcode::BITCAST, 128);
mi2.def_reg = Some(3);
mi2.operands.push(CombineOperand::Reg(4));
let instrs = vec![mi1, mi2];
let result = c.try_bitcast_chain(&instrs[0], 0, &instrs);
assert!(result.is_none());
}
#[test]
fn test_constant_fold_paddd() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi = make_mi(X86Opcode::PADDD, 128, 1, &[2, 3]);
mi.constant_data = Some(ConstantVector::new(vec![1, 2, 3, 4], 32, false));
let result = c.try_constant_fold(&mi);
if result.is_some() {
assert_eq!(result.unwrap().kind, CombineKind::ConstantFold);
}
}
#[test]
fn test_constant_fold_no_constants() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::PADDD, 128, 1, &[2, 3]);
assert!(c.try_constant_fold(&mi).is_none());
}
#[test]
fn test_constant_fold_disabled() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.enable_constant_fold = false;
let mut mi = make_mi(X86Opcode::PADDD, 128, 1, &[2, 3]);
mi.constant_data = Some(ConstantVector::zero(4, 32));
assert!(c.try_constant_fold(&mi).is_none());
}
#[test]
fn test_horizontal_add_recognized() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::ADDPS, 128, 1, &[2, 3]);
let result = c.try_horizontal_op(&mi, 0, &[]);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, CombineKind::HorizontalOp);
}
#[test]
fn test_horizontal_sub_recognized() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::SUBPS, 128, 1, &[2, 3]);
let result = c.try_horizontal_op(&mi, 0, &[]);
assert!(result.is_some());
}
#[test]
fn test_horizontal_disabled() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.enable_horizontal = false;
let mi = make_mi(X86Opcode::ADDPS, 128, 1, &[2, 3]);
assert!(c.try_horizontal_op(&mi, 0, &[]).is_none());
}
#[test]
fn test_extract_insert_merge() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::EXTRACT_SUBVEC, 128);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
mi1.operands.push(CombineOperand::Imm(0));
let mut mi2 = MachineCombineInstr::new(X86Opcode::INSERT_SUBVEC, 256);
mi2.def_reg = Some(3);
mi2.operands.push(CombineOperand::Reg(4));
mi2.operands.push(CombineOperand::Reg(1)); mi2.operands.push(CombineOperand::Imm(0));
let instrs = vec![mi1, mi2];
let result = c.try_extract_insert_merge(&instrs[0], 0, &instrs);
assert!(result.is_some());
assert_eq!(result.unwrap().kind, CombineKind::ExtractInsertMerge);
}
#[test]
fn test_run_on_block_empty() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let results = c.run_on_block(&[]);
assert!(results.is_empty());
}
#[test]
fn test_run_on_block_identity_shuffle() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
let results = c.run_on_block(&[mi]);
assert_eq!(results.len(), 1);
assert_eq!(results[0].kind, CombineKind::IdentityShuffle);
}
#[test]
fn test_run_on_block_nop_only() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::NOP, 0, 1, &[]);
let results = c.run_on_block(&[mi]);
assert!(results.is_empty());
}
#[test]
fn test_clear() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.stats.shuffle_shuffle = 5;
c.stats.total_eliminated = 5;
c.clear();
assert_eq!(c.stats.shuffle_shuffle, 0);
assert_eq!(c.stats.total_eliminated, 0);
}
#[test]
fn test_machine_combine_instr_new() {
let mi = MachineCombineInstr::new(X86Opcode::PSHUFD, 128);
assert_eq!(mi.opcode, X86Opcode::PSHUFD);
assert_eq!(mi.vec_width, 128);
assert!(mi.operands.is_empty());
}
#[test]
fn test_is_shuffle_pshufd() {
let mi = MachineCombineInstr::new(X86Opcode::PSHUFD, 128);
assert!(mi.is_shuffle());
}
#[test]
fn test_is_shuffle_shufps() {
let mi = MachineCombineInstr::new(X86Opcode::SHUFPS, 128);
assert!(mi.is_shuffle());
}
#[test]
fn test_is_not_shuffle() {
let mi = MachineCombineInstr::new(X86Opcode::ADDPS, 128);
assert!(!mi.is_shuffle());
}
#[test]
fn test_is_blend() {
let mi = MachineCombineInstr::new(X86Opcode::BLENDPS, 128);
assert!(mi.is_blend());
}
#[test]
fn test_is_not_blend() {
let mi = MachineCombineInstr::new(X86Opcode::ADDPS, 128);
assert!(!mi.is_blend());
}
#[test]
fn test_is_bitcast() {
let mi = MachineCombineInstr::new(X86Opcode::BITCAST, 128);
assert!(mi.is_bitcast());
}
#[test]
fn test_is_insert_subvector() {
let mi = MachineCombineInstr::new(X86Opcode::INSERT_SUBVEC, 256);
assert!(mi.is_insert_subvector());
}
#[test]
fn test_is_extract_subvector() {
let mi = MachineCombineInstr::new(X86Opcode::EXTRACT_SUBVEC, 256);
assert!(mi.is_extract_subvector());
}
#[test]
fn test_combine_emitter_new() {
let emitter = CombineEmitter::new();
assert!(emitter.sequence.is_empty());
}
#[test]
fn test_combine_emitter_default() {
let emitter = CombineEmitter::default();
assert!(emitter.sequence.is_empty());
}
#[test]
fn test_combine_emitter_alloc_temp() {
let mut emitter = CombineEmitter::new();
let t1 = emitter.alloc_temp();
let t2 = emitter.alloc_temp();
assert_ne!(t1, t2);
}
#[test]
fn test_combine_emitter_emit_shuffle() {
let mut emitter = CombineEmitter::new();
let result = CombineResult {
kind: CombineKind::ShuffleShuffle,
original_opcodes: vec![X86Opcode::PSHUFD],
combined_opcode: X86Opcode::PSHUFD,
description: "test".into(),
instructions_eliminated: 1,
new_shuffle_mask: Some(CombineShuffleMask::identity(4, 32)),
new_blend_mask: None,
constant_result: None,
};
emitter.emit_combine(&result);
assert!(!emitter.sequence.is_empty());
}
#[test]
fn test_combine_operand_equality() {
assert_eq!(CombineOperand::Reg(1), CombineOperand::Reg(1));
assert_ne!(CombineOperand::Reg(1), CombineOperand::Reg(2));
assert_eq!(CombineOperand::Imm(42), CombineOperand::Imm(42));
assert_eq!(CombineOperand::Mem(0x1000), CombineOperand::Mem(0x1000));
assert_eq!(
CombineOperand::SubvecIndex(3),
CombineOperand::SubvecIndex(3)
);
}
#[test]
fn test_combine_kind_display() {
assert_eq!(CombineKind::ShuffleShuffle.to_string(), "shuffle+shuffle");
assert_eq!(CombineKind::IdentityShuffle.to_string(), "identity→noop");
}
#[test]
fn test_combine_shuffle_mask_debug() {
let mask = CombineShuffleMask::identity(4, 32);
let s = format!("{:?}", mask);
assert!(s.contains("indices"));
}
#[test]
fn test_combine_result_debug() {
let result = CombineResult {
kind: CombineKind::ConstantFold,
original_opcodes: vec![X86Opcode::PADDD],
combined_opcode: X86Opcode::PADDD,
description: "test".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
};
let s = format!("{:?}", result);
assert!(!s.is_empty());
}
#[test]
fn test_combine_shuffle_mask_clone() {
let mask = CombineShuffleMask::identity(4, 32);
let mask2 = mask.clone();
assert_eq!(mask2.indices, vec![0, 1, 2, 3]);
}
#[test]
fn test_blend_mask_clone() {
let mask = BlendMask::all_first(4);
let mask2 = mask.clone();
assert!(mask2.is_all_first());
}
#[test]
fn test_constant_vector_clone() {
let cv = ConstantVector::new(vec![1, 2, 3, 4], 32, false);
let cv2 = cv.clone();
assert_eq!(cv2.elements, vec![1, 2, 3, 4]);
}
#[test]
fn test_combine_all_identity_shuffles() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
let results = c.run_on_block(&[mi.clone(), mi]);
assert!(!results.is_empty());
for r in &results {
assert_eq!(r.kind, CombineKind::IdentityShuffle);
}
}
#[test]
fn test_multiple_iterations_converge() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let shuf1 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
let mut shuf2 = make_shuffle_mi(X86Opcode::PSHUFD, 128, 3, 1, 0xE4);
shuf2.operands[0] = CombineOperand::Reg(1);
let instrs = vec![shuf1, shuf2];
let results = c.run_on_block(&instrs);
assert!(!results.is_empty());
}
#[test]
fn test_block_with_only_non_vector_ops() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::ADD32rr, 0, 1, &[2, 3]);
let results = c.run_on_block(&[mi]);
assert!(results.is_empty());
}
#[test]
fn test_combine_default_does_not_panic() {
let c = X86VectorCombine::default();
assert!(c.enable_shuffle_compose);
assert_eq!(c.stats.total_eliminated, 0);
}
#[test]
fn test_run_on_block_iteration_limit() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.max_iterations = 1;
let shuf = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
let results = c.run_on_block(&[shuf]);
assert_eq!(results.len(), 1);
}
#[test]
fn test_combine_respects_max_per_block() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.max_combines_per_block = 1;
let instructions: Vec<_> = (0..10)
.map(|i| make_shuffle_mi(X86Opcode::PSHUFD, 128, i + 1, i + 10, 0xE4))
.collect();
let results = c.run_on_block(&instructions);
assert!(results.len() <= 1);
}
#[test]
fn test_constant_vector_is_zero() {
let cv = ConstantVector::new(vec![0, 0, 0, 0], 32, false);
assert!(cv.is_zero());
}
#[test]
fn test_constant_vector_not_zero() {
let cv = ConstantVector::new(vec![1, 0, 0, 0], 32, false);
assert!(!cv.is_zero());
}
#[test]
fn test_combine_kind_debug() {
for kind in &[
CombineKind::ShuffleShuffle,
CombineKind::InsertExtractNoop,
CombineKind::BroadcastOp,
CombineKind::BlendBlend,
CombineKind::ConstantFold,
] {
let s = format!("{:?}", kind);
assert!(!s.is_empty());
}
}
#[test]
fn test_shuffle_mask_compose_triple() {
let rev = CombineShuffleMask {
indices: vec![3, 2, 1, 0],
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
};
let composed = rev.compose(&rev).unwrap();
let triple = composed.compose(&rev).unwrap();
assert!(triple.is_reverse());
}
#[test]
fn test_blend_compose_alternating() {
let b1 = BlendMask {
selects: vec![true, false, true, false],
num_elements: 4,
};
let b2 = BlendMask {
selects: vec![false, true, false, true],
num_elements: 4,
};
let composed = b1.compose(&b2).unwrap();
assert!(composed.is_all_second());
}
#[test]
fn test_constant_xor_same_is_zero() {
let a = ConstantVector::new(vec![0xAB, 0xCD, 0xEF, 0x12], 8, false);
let result = a.xor(&a).unwrap();
assert!(result.is_zero());
}
#[test]
fn test_constant_sub_self_is_zero() {
let a = ConstantVector::new(vec![5, 10, 15, 20], 32, false);
let result = a.sub(&a).unwrap();
assert!(result.is_zero());
}
#[test]
fn test_shufps_compose() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::SHUFPS, 128);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
mi1.operands.push(CombineOperand::Reg(2));
mi1.operands.push(CombineOperand::Imm(0x1B));
let mut mi2 = MachineCombineInstr::new(X86Opcode::SHUFPS, 128);
mi2.def_reg = Some(3);
mi2.operands.push(CombineOperand::Reg(1));
mi2.operands.push(CombineOperand::Reg(1));
mi2.operands.push(CombineOperand::Imm(0x1B));
let instrs = vec![mi1, mi2];
assert!(c.try_shuffle_compose(&instrs[0], 0, &instrs).is_some());
}
#[test]
fn test_vblendps_compose() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi1 = MachineCombineInstr::new(X86Opcode::VBLENDPS, 256);
mi1.def_reg = Some(1);
mi1.operands.push(CombineOperand::Reg(2));
mi1.operands.push(CombineOperand::Reg(3));
mi1.operands.push(CombineOperand::Imm(0xAA));
let mut mi2 = MachineCombineInstr::new(X86Opcode::VBLENDPS, 256);
mi2.def_reg = Some(4);
mi2.operands.push(CombineOperand::Reg(1));
mi2.operands.push(CombineOperand::Reg(5));
mi2.operands.push(CombineOperand::Imm(0xCC));
let instrs = vec![mi1, mi2];
assert!(c.try_blend_compose(&instrs[0], 0, &instrs).is_some());
}
#[test]
fn test_haddpd_recognized() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::ADDPD, 128, 1, &[2, 3]);
assert!(c.try_horizontal_op(&mi, 0, &[]).is_some());
}
#[test]
fn test_hsubpd_recognized() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::SUBPD, 128, 1, &[2, 3]);
assert!(c.try_horizontal_op(&mi, 0, &[]).is_some());
}
#[test]
fn test_get_shuffle_mask_shufps() {
let mut mi = MachineCombineInstr::new(X86Opcode::SHUFPS, 128);
mi.operands.push(CombineOperand::Reg(1));
mi.operands.push(CombineOperand::Reg(1));
mi.operands.push(CombineOperand::Imm(0x1B));
let mask = mi.get_shuffle_mask().unwrap();
assert_eq!(mask.indices, vec![3, 2, 1, 0]);
}
#[test]
fn test_get_blend_mask_vpblendw() {
let mut mi = MachineCombineInstr::new(X86Opcode::VPBLENDW, 128);
mi.operands.push(CombineOperand::Reg(1));
mi.operands.push(CombineOperand::Reg(2));
mi.operands.push(CombineOperand::Imm(0x55));
let m = mi.get_blend_mask().unwrap();
assert_eq!(m.num_elements, 8);
}
#[test]
fn test_clear_preserves_config() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.enable_shuffle_compose = false;
c.stats.total_eliminated = 10;
c.clear();
assert_eq!(c.stats.total_eliminated, 0);
assert!(!c.enable_shuffle_compose);
}
#[test]
fn test_stats_all_fields_summary() {
let mut s = CombineStats::default();
s.shuffle_shuffle = 1;
s.insert_extract_noop = 2;
s.broadcast_op = 3;
s.blend_blend = 4;
s.concat_simplify = 5;
s.extract_simplify = 6;
s.bitcast_chain = 7;
s.identity_shuffle = 8;
s.duplicate_shuffle = 9;
s.constant_fold = 10;
s.horizontal_op = 11;
s.extract_insert_merge = 12;
s.permute_permute = 13;
s.unpack_pack_cancel = 14;
s.total_eliminated = 105;
s.candidates_examined = 500;
let summary = s.summary();
assert!(summary.contains("1 shuffle+shuffle"));
assert!(summary.contains("10 const"));
assert!(summary.contains("105 eliminated"));
}
#[test]
fn test_emitter_shuffle_imm() {
let mut emitter = CombineEmitter::new();
let mask = CombineShuffleMask::identity(4, 32);
emitter.emit_shuffle(X86Opcode::PSHUFD as u32, &mask);
assert_eq!(emitter.sequence.len(), 1);
assert_eq!(emitter.sequence[0].imm, Some(0xE4));
}
#[test]
fn test_emitter_blend_imm() {
let mut emitter = CombineEmitter::new();
let mask = BlendMask {
selects: vec![true, false, true, false],
num_elements: 4,
};
emitter.emit_blend(X86Opcode::BLENDPS as u32, &mask);
assert_eq!(emitter.sequence[0].imm, Some(0b0101));
}
#[test]
fn test_emitter_identity_noop() {
let mut emitter = CombineEmitter::new();
let result = CombineResult {
kind: CombineKind::IdentityShuffle,
original_opcodes: vec![X86Opcode::PSHUFD],
combined_opcode: X86Opcode::PSHUFD,
description: "eliminated".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: None,
};
emitter.emit_combine(&result);
assert!(emitter.sequence.is_empty());
}
#[test]
fn test_all_kinds_unique_display() {
let kinds = [
CombineKind::ShuffleShuffle,
CombineKind::InsertExtractNoop,
CombineKind::BroadcastOp,
CombineKind::BlendBlend,
CombineKind::ConcatSimplify,
CombineKind::ExtractSimplify,
CombineKind::BitcastChain,
CombineKind::IdentityShuffle,
CombineKind::DuplicateShuffle,
CombineKind::ConstantFold,
CombineKind::HorizontalOp,
CombineKind::ExtractInsertMerge,
CombineKind::PermutePermute,
CombineKind::UnpackPackCancel,
CombineKind::ExtractExtract,
];
let mut strings: Vec<String> = kinds.iter().map(|k| k.to_string()).collect();
strings.sort();
strings.dedup();
assert_eq!(strings.len(), kinds.len());
}
#[test]
fn test_is_insert_vinsertf128() {
let mi = MachineCombineInstr::new(X86Opcode::VINSERTF128, 256);
assert!(mi.is_insert_subvector());
}
#[test]
fn test_is_extract_vextractf128() {
let mi = MachineCombineInstr::new(X86Opcode::VEXTRACTF128, 256);
assert!(mi.is_extract_subvector());
}
#[test]
fn test_shuffle_compose_with_undef() {
let mask1 = CombineShuffleMask {
indices: vec![0, u8::MAX, 2, u8::MAX],
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
};
let mask2 = CombineShuffleMask::identity(4, 32);
assert!(mask1.compose(&mask2).is_some());
}
#[test]
fn test_vshufps_is_shuffle() {
let mi = MachineCombineInstr::new(X86Opcode::VSHUFPS, 256);
assert!(mi.is_shuffle());
}
#[test]
fn test_vshufpd_is_shuffle() {
let mi = MachineCombineInstr::new(X86Opcode::VSHUFPD, 256);
assert!(mi.is_shuffle());
}
#[test]
fn test_all_ones_64bit() {
let cv = ConstantVector::all_ones(2, 64);
assert!(cv.is_all_ones());
}
#[test]
fn test_all_ones_8bit() {
let cv = ConstantVector::all_ones(16, 8);
assert!(cv.is_all_ones());
assert_eq!(cv.elements[0], 0xFF);
}
#[test]
fn test_add_wrapping() {
let a = ConstantVector::new(vec![0x7FFFFFFF, 0, 0, 0], 32, false);
let b = ConstantVector::new(vec![1, 0, 0, 0], 32, false);
let sum = a.add(&b).unwrap();
assert_eq!(sum.elements[0] as u32, 0x80000000u32);
}
#[test]
fn test_mul_wrapping() {
let a = ConstantVector::new(vec![0x10000, 0, 0, 0], 32, false);
let b = ConstantVector::new(vec![0x10000, 0, 0, 0], 32, false);
let prod = a.mul(&b).unwrap();
assert_eq!(prod.elements[0], 0);
}
#[test]
fn test_make_combine_functions() {
let st = make_subtarget();
let c1 = make_x86_vector_combine(st);
assert!(c1.enable_shuffle_compose);
let c2 = make_x86_vector_combine_aggressive(st);
assert_eq!(c2.max_iterations, 8);
let c3 = make_x86_vector_combine_conservative(st);
assert!(!c3.enable_constant_fold);
}
#[test]
fn test_subvec_index_operand() {
assert_eq!(
CombineOperand::SubvecIndex(2),
CombineOperand::SubvecIndex(2)
);
assert_ne!(CombineOperand::SubvecIndex(2), CombineOperand::Imm(2));
}
#[test]
fn test_emitted_instr_debug_fmt() {
let instr = CombineEmittedInstr {
opcode: 42,
def_reg: 1,
use_regs: vec![2, 3],
imm: Some(99),
comment: "test".into(),
};
let s = format!("{:?}", instr);
assert!(!s.is_empty());
}
#[test]
fn test_mi_src_reg_bounds() {
let mut mi = MachineCombineInstr::new(X86Opcode::PSHUFD, 128);
mi.operands.push(CombineOperand::Reg(5));
mi.operands.push(CombineOperand::Reg(6));
mi.operands.push(CombineOperand::Imm(0xE4));
assert_eq!(mi.src_reg(0), Some(5));
assert_eq!(mi.src_reg(1), Some(6));
assert_eq!(mi.src_reg(2), None);
assert_eq!(mi.src_reg(99), None);
}
#[test]
fn test_mi_imm_op_bounds() {
let mut mi = MachineCombineInstr::new(X86Opcode::PSHUFD, 128);
mi.operands.push(CombineOperand::Reg(2));
mi.operands.push(CombineOperand::Reg(2));
mi.operands.push(CombineOperand::Imm(0x1B));
assert_eq!(mi.imm_op(2), Some(0x1B));
assert_eq!(mi.imm_op(0), None);
}
#[test]
fn test_combine_run_on_mixed_block() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let shuf = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
let hadd = make_mi(X86Opcode::ADDPS, 128, 3, &[4, 5]);
let nop = make_mi(X86Opcode::NOP, 0, 6, &[]);
let results = c.run_on_block(&[shuf, hadd, nop]);
assert!(!results.is_empty());
}
#[test]
fn test_combine_convergence() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
c.max_iterations = 3;
let shuf = make_shuffle_mi(X86Opcode::PSHUFD, 128, 1, 2, 0xE4);
let results = c.run_on_block(&[shuf.clone(), shuf]);
assert!(!results.is_empty());
}
#[test]
fn test_combine_mi_is_shuffle_vperm2f128() {
assert!(MachineCombineInstr::new(X86Opcode::VPERM2F128, 256).is_shuffle());
}
#[test]
fn test_combine_mi_is_blend_blendpd() {
assert!(MachineCombineInstr::new(X86Opcode::BLENDPD, 128).is_blend());
}
#[test]
fn test_combine_mi_is_blend_vblendpd() {
assert!(MachineCombineInstr::new(X86Opcode::VBLENDPD, 256).is_blend());
}
#[test]
fn test_combine_mi_is_insert_vinserti128() {
assert!(MachineCombineInstr::new(X86Opcode::VINSERTI128, 256).is_insert_subvector());
}
#[test]
fn test_combine_mi_is_extract_vextracti128() {
assert!(MachineCombineInstr::new(X86Opcode::VEXTRACTI128, 256).is_extract_subvector());
}
#[test]
fn test_combine_constant_fold_paddq() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi = make_mi(X86Opcode::PADDQ, 128, 1, &[2, 3]);
mi.constant_data = Some(ConstantVector::new(vec![100, 200], 64, false));
let result = c.try_constant_fold(&mi);
if result.is_some() {
assert_eq!(result.unwrap().kind, CombineKind::ConstantFold);
}
}
#[test]
fn test_combine_constant_fold_psubq() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi = make_mi(X86Opcode::PSUBQ, 128, 1, &[2, 3]);
mi.constant_data = Some(ConstantVector::zero(2, 64));
let result = c.try_constant_fold(&mi);
if result.is_some() {
assert_eq!(result.unwrap().kind, CombineKind::ConstantFold);
}
}
#[test]
fn test_combine_constant_fold_psubd() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi = make_mi(X86Opcode::PSUBD, 128, 1, &[2, 3]);
mi.constant_data = Some(ConstantVector::new(vec![10, 20, 30, 40], 32, false));
let result = c.try_constant_fold(&mi);
if result.is_some() {
assert_eq!(result.unwrap().kind, CombineKind::ConstantFold);
}
}
#[test]
fn test_combine_constant_fold_paddw() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mut mi = make_mi(X86Opcode::PADDW, 128, 1, &[2, 3]);
mi.constant_data = Some(ConstantVector::new(vec![1, 2, 3, 4, 5, 6, 7, 8], 16, false));
let result = c.try_constant_fold(&mi);
if result.is_some() {
assert_eq!(result.unwrap().kind, CombineKind::ConstantFold);
}
}
#[test]
fn test_combine_constant_fold_non_constant_op() {
let st = make_subtarget();
let mut c = X86VectorCombine::new(st);
let mi = make_mi(X86Opcode::ADDPS, 128, 1, &[2, 3]);
assert!(c.try_constant_fold(&mi).is_none());
}
#[test]
fn test_combine_result_with_blend() {
let mask = BlendMask::all_first(4);
let r = CombineResult {
kind: CombineKind::BlendBlend,
original_opcodes: vec![X86Opcode::BLENDPS],
combined_opcode: X86Opcode::BLENDPS,
description: "test".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: Some(mask),
constant_result: None,
};
assert!(r.new_blend_mask.is_some());
}
#[test]
fn test_combine_result_with_constant() {
let cv = ConstantVector::zero(4, 32);
let r = CombineResult {
kind: CombineKind::ConstantFold,
original_opcodes: vec![X86Opcode::PADDD],
combined_opcode: X86Opcode::PADDD,
description: "folded".into(),
instructions_eliminated: 1,
new_shuffle_mask: None,
new_blend_mask: None,
constant_result: Some(cv),
};
assert!(r.constant_result.is_some());
}
#[test]
fn test_combine_shuffle_mask_to_pshufd_oob() {
let mask = CombineShuffleMask {
indices: vec![4, 0, 0, 0],
num_src_elements: 4,
num_dst_elements: 4,
elem_bits: 32,
};
assert_eq!(mask.to_pshufd_imm(), None); }
#[test]
fn test_combine_blend_mask_to_imm_8elem() {
let mask = BlendMask {
selects: vec![true, false, true, false, true, false, true, false],
num_elements: 8,
};
assert_eq!(mask.to_imm(), Some(0x55));
}
#[test]
fn test_combine_emitter_constant_load() {
let mut emitter = CombineEmitter::new();
let cv = ConstantVector::new(vec![42, 42, 42, 42], 32, false);
emitter.emit_constant_load(&cv);
assert_eq!(emitter.sequence.len(), 1);
assert_eq!(emitter.sequence[0].opcode, X86Opcode::MOVDQA as u32);
}
#[test]
fn test_combine_default_all_config() {
let c = X86VectorCombine::default();
assert!(c.enable_shuffle_compose);
assert!(c.enable_insert_extract);
assert!(c.enable_broadcast_op);
assert!(c.enable_blend_compose);
assert!(c.enable_identity_shuffle);
assert!(c.enable_constant_fold);
assert!(c.enable_horizontal);
assert!(c.enable_duplicate_shuffle);
assert!(c.enable_bitcast_chain);
assert_eq!(c.max_iterations, 4);
assert_eq!(c.max_combines_per_block, 128);
}
#[test]
fn test_combine_mi_dst_reg_none() {
let mi = MachineCombineInstr::new(X86Opcode::PSHUFD, 128);
assert_eq!(mi.dst_reg(), None);
}
#[test]
fn test_combine_mi_dst_reg_some() {
let mut mi = MachineCombineInstr::new(X86Opcode::PSHUFD, 128);
mi.def_reg = Some(5);
assert_eq!(mi.dst_reg(), Some(5));
}
#[test]
fn test_combine_kind_clone_eq() {
let k1 = CombineKind::ShuffleShuffle;
let k2 = k1;
assert_eq!(k1, k2);
}
#[test]
fn test_combine_blend_is_all_first_true() {
assert!(BlendMask::all_first(4).is_all_first());
assert!(!BlendMask::all_first(4).is_all_second());
}
#[test]
fn test_combine_blend_is_all_second_true() {
assert!(BlendMask::all_second(4).is_all_second());
assert!(!BlendMask::all_second(4).is_all_first());
}
#[test]
fn test_combine_constant_or_different_elem_bits() {
let a = ConstantVector::new(vec![1], 32, false);
let b = ConstantVector::new(vec![1], 16, false);
assert!(a.or(&b).is_none());
}
#[test]
fn test_combine_constant_xor_different_sizes() {
let a = ConstantVector::new(vec![1, 2], 32, false);
let b = ConstantVector::new(vec![1, 2, 3], 32, false);
assert!(a.xor(&b).is_none());
}
}