use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DAGOpcode {
Add,
Sub,
Neg,
Mul,
SDiv,
UDiv,
And,
Or,
Xor,
Shl,
Sra,
Srl,
FAdd,
FSub,
FMul,
FDiv,
SetCC,
ICmp,
FCmp,
BrCond,
Select,
Load,
Store,
ZExt,
SExt,
AExt, Trunc,
BitCast,
BuildVector,
ExtractElement,
InsertElement,
ExtractSubvector,
InsertSubvector,
ShuffleVector,
ConcatVectors,
X86Cmp,
X86Cmov,
X86SetCC,
X86BrCond,
X86SetCCCarry,
X86Fmadd,
X86Fmsub,
X86Fnmadd,
X86Fnmsub,
X86Movsd,
X86Movss,
X86Movups,
X86Movaps,
X86Pshufd,
X86Shufps,
X86Movsx,
X86Movzx,
Constant,
ConstantFP,
Undef,
CopyFromReg,
CopyToReg,
TokenFactor,
EntryToken,
FrameIndex,
GlobalAddress,
TargetConstant,
}
impl DAGOpcode {
pub fn is_load(&self) -> bool {
matches!(self, Self::Load)
}
pub fn is_store(&self) -> bool {
matches!(self, Self::Store)
}
pub fn is_bitcast(&self) -> bool {
matches!(self, Self::BitCast)
}
pub fn is_constant(&self) -> bool {
matches!(self, Self::Constant | Self::ConstantFP)
}
pub fn is_undef(&self) -> bool {
matches!(self, Self::Undef)
}
pub fn is_vector_op(&self) -> bool {
matches!(
self,
Self::BuildVector
| Self::ExtractElement
| Self::InsertElement
| Self::ExtractSubvector
| Self::InsertSubvector
| Self::ShuffleVector
| Self::ConcatVectors
)
}
pub fn is_x86_op(&self) -> bool {
matches!(
self,
Self::X86Cmp
| Self::X86Cmov
| Self::X86SetCC
| Self::X86BrCond
| Self::X86SetCCCarry
| Self::X86Fmadd
| Self::X86Fmsub
| Self::X86Fnmadd
| Self::X86Fnmsub
| Self::X86Movsd
| Self::X86Movss
| Self::X86Movups
| Self::X86Movaps
| Self::X86Pshufd
| Self::X86Shufps
| Self::X86Movsx
| Self::X86Movzx
)
}
}
#[derive(Debug, Clone)]
pub struct DAGNodeRef {
pub id: u32,
pub opcode: DAGOpcode,
pub operands: Vec<u32>, pub value_type: u32, pub has_one_use: bool,
pub flags: NodeFlags,
}
#[derive(Debug, Clone, Default)]
pub struct NodeFlags {
pub has_no_unsigned_wrap: bool,
pub has_no_signed_wrap: bool,
pub is_exact: bool,
pub is_volatile: bool,
pub alignment: u32,
pub may_load: bool,
pub may_store: bool,
}
impl DAGNodeRef {
pub fn new(id: u32, opcode: DAGOpcode, operands: Vec<u32>, value_type: u32) -> Self {
Self {
id,
opcode,
operands,
value_type,
has_one_use: false,
flags: NodeFlags::default(),
}
}
}
#[derive(Debug, Clone)]
pub enum CombineAction {
Replace(DAGNodeRef),
ReplaceWith(Vec<DAGNodeRef>),
Delete,
NoChange,
Modified,
}
impl CombineAction {
pub fn changed(&self) -> bool {
!matches!(self, Self::NoChange)
}
}
#[derive(Debug, Clone)]
pub struct CombineResult {
pub action: CombineAction,
pub description: String,
pub benefit: i32, }
impl CombineResult {
pub fn no_change() -> Self {
Self {
action: CombineAction::NoChange,
description: String::new(),
benefit: 0,
}
}
pub fn replaced(node: DAGNodeRef, desc: &str, benefit: i32) -> Self {
Self {
action: CombineAction::Replace(node),
description: desc.to_string(),
benefit,
}
}
pub fn replaced_with(nodes: Vec<DAGNodeRef>, desc: &str, benefit: i32) -> Self {
Self {
action: CombineAction::ReplaceWith(nodes),
description: desc.to_string(),
benefit,
}
}
pub fn deleted(desc: &str, benefit: i32) -> Self {
Self {
action: CombineAction::Delete,
description: desc.to_string(),
benefit,
}
}
pub fn modified(desc: &str, benefit: i32) -> Self {
Self {
action: CombineAction::Modified,
description: desc.to_string(),
benefit,
}
}
}
#[derive(Debug, Clone)]
pub struct CombineDAG {
pub nodes: HashMap<u32, DAGNodeRef>,
pub next_id: u32,
pub root_id: Option<u32>,
pub use_counts: HashMap<u32, u32>,
}
impl CombineDAG {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
next_id: 0,
root_id: None,
use_counts: HashMap::new(),
}
}
pub fn allocate_id(&mut self) -> u32 {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn add_node(&mut self, node: DAGNodeRef) -> u32 {
let id = node.id;
for &op_id in &node.operands {
*self.use_counts.entry(op_id).or_insert(0) += 1;
}
self.nodes.insert(id, node);
id
}
pub fn get_node(&self, id: u32) -> Option<&DAGNodeRef> {
self.nodes.get(&id)
}
pub fn get_node_mut(&mut self, id: u32) -> Option<&mut DAGNodeRef> {
self.nodes.get_mut(&id)
}
pub fn replace_node(&mut self, old_id: u32, new_node: DAGNodeRef) {
self.nodes.remove(&old_id);
for &op_id in &new_node.operands {
*self.use_counts.entry(op_id).or_insert(0) += 1;
}
self.nodes.insert(new_node.id, new_node);
}
pub fn delete_node(&mut self, id: u32) {
if let Some(node) = self.nodes.remove(&id) {
for &op_id in &node.operands {
if let Some(count) = self.use_counts.get_mut(&op_id) {
*count = count.saturating_sub(1);
}
}
}
}
pub fn use_count(&self, id: u32) -> u32 {
self.use_counts.get(&id).copied().unwrap_or(0)
}
pub fn has_one_use(&self, id: u32) -> bool {
self.use_count(id) <= 1
}
}
impl Default for CombineDAG {
fn default() -> Self {
Self::new()
}
}
pub struct X86DAGCombineDeep {
pub dag: CombineDAG,
pub num_combined: u32,
pub modified: bool,
max_iterations: u32,
worklist: Vec<u32>,
visited: HashSet<u32>,
pub stats: X86CombineStats,
pub aggressive: bool,
pub has_sse2: bool,
pub has_sse41: bool,
pub has_avx: bool,
pub has_avx2: bool,
pub has_avx512: bool,
pub has_fma: bool,
pub has_bmi: bool,
pub has_bmi2: bool,
pub is_64bit: bool,
}
#[derive(Debug, Clone, Default)]
pub struct X86CombineStats {
pub load_pairs: u32,
pub store_pairs: u32,
pub shuffle_folds: u32,
pub bitcast_chains: u32,
pub sign_extend_inreg: u32,
pub zero_extend_inreg: u32,
pub any_extend: u32,
pub cmov_patterns: u32,
pub brcond_patterns: u32,
pub setcc_carry: u32,
pub mov_combines: u32,
pub fma_patterns: u32,
pub undef_folds: u32,
pub shuffle_canonicalizations: u32,
pub address_mode_patterns: u32,
pub nodes_processed: u32,
pub nodes_combined_total: u32,
pub iterations: u32,
}
impl X86CombineStats {
pub fn new() -> Self {
Self::default()
}
pub fn summary(&self) -> String {
format!(
"combined={} loads={} stores={} shuffles={} bitcasts={} ext={}/{}/{} cmov={} fma={} undef={} addr={} iters={}",
self.nodes_combined_total,
self.load_pairs,
self.store_pairs,
self.shuffle_folds,
self.bitcast_chains,
self.sign_extend_inreg,
self.zero_extend_inreg,
self.any_extend,
self.cmov_patterns,
self.fma_patterns,
self.undef_folds,
self.address_mode_patterns,
self.iterations,
)
}
}
impl X86DAGCombineDeep {
pub fn new(is_64bit: bool) -> Self {
Self {
dag: CombineDAG::new(),
num_combined: 0,
modified: false,
max_iterations: 4,
worklist: Vec::new(),
visited: HashSet::new(),
stats: X86CombineStats::new(),
aggressive: false,
has_sse2: true,
has_sse41: true,
has_avx: true,
has_avx2: true,
has_avx512: false,
has_fma: true,
has_bmi: true,
has_bmi2: true,
is_64bit,
}
}
pub fn with_aggressive(mut self, yes: bool) -> Self {
self.aggressive = yes;
self.max_iterations = if yes { 8 } else { 4 };
self
}
pub fn with_avx512(mut self) -> Self {
self.has_avx512 = true;
self
}
pub fn without_fma(mut self) -> Self {
self.has_fma = false;
self
}
pub fn combine(&mut self) -> bool {
self.stats.iterations = 0;
self.modified = false;
for _iteration in 0..self.max_iterations {
self.stats.iterations += 1;
self.worklist = self.dag.nodes.keys().copied().collect();
self.visited.clear();
let mut iteration_changed = false;
while let Some(node_id) = self.worklist.pop() {
if self.visited.contains(&node_id) {
continue;
}
self.visited.insert(node_id);
self.stats.nodes_processed += 1;
let result = self.combine_node(node_id);
if result.action.changed() {
iteration_changed = true;
self.modified = true;
self.num_combined += 1;
self.stats.nodes_combined_total += 1;
}
}
if !iteration_changed {
break; }
}
self.modified
}
fn combine_node(&mut self, node_id: u32) -> CombineResult {
let node = match self.dag.get_node(node_id) {
Some(n) => n.clone(),
None => return CombineResult::no_change(),
};
match node.opcode {
DAGOpcode::Load => self.combine_load_to_load_pair(&node),
DAGOpcode::Store => self.combine_store_to_store_pair(&node),
DAGOpcode::ExtractElement | DAGOpcode::InsertElement => {
self.combine_extract_to_shuffle(&node)
}
DAGOpcode::ExtractSubvector | DAGOpcode::InsertSubvector => {
self.combine_subvector_to_shuffle(&node)
}
DAGOpcode::BitCast => self.combine_bitcast_chain(&node),
DAGOpcode::And => {
let r = self.combine_sign_extend_inreg(&node);
if r.action.changed() {
return r;
}
self.combine_zero_extend_inreg(&node)
}
DAGOpcode::Select => self.combine_select_to_cmov(&node),
DAGOpcode::BrCond => self.combine_brcond_to_x86_brcond(&node),
DAGOpcode::X86Cmp => self.combine_cmp_to_setcc_carry(&node),
DAGOpcode::X86Movsd | DAGOpcode::X86Movss => self.combine_scalar_moves_to_vector(&node),
DAGOpcode::FSub => self.combine_fadd_fsub_to_fmsub(&node),
DAGOpcode::FAdd => self.combine_fmul_fadd_to_fmadd(&node),
_ => {
if self.has_undef_operand(&node) {
self.combine_undef_fold(&node)
} else {
CombineResult::no_change()
}
}
}
}
fn combine_load_to_load_pair(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.operands.len() < 2 {
return CombineResult::no_change();
}
let _chain_id = node.operands[0];
let ptr_id = node.operands[1];
if let Some(ptr_node) = self.dag.get_node(ptr_id) {
if ptr_node.opcode == DAGOpcode::Add {
self.stats.load_pairs += 1;
return CombineResult::modified("consider load pair: adjacent address", 1);
}
}
CombineResult::no_change()
}
fn combine_store_to_store_pair(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.operands.len() < 3 {
return CombineResult::no_change();
}
let ptr_id = node.operands[2];
if let Some(ptr_node) = self.dag.get_node(ptr_id) {
if ptr_node.opcode == DAGOpcode::Add {
self.stats.store_pairs += 1;
return CombineResult::modified("consider store pair: adjacent address", 1);
}
}
CombineResult::no_change()
}
fn combine_extract_to_shuffle(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode == DAGOpcode::ExtractElement {
if self.dag.use_count(node.id) == 1 {
self.stats.shuffle_folds += 1;
return CombineResult::modified("fold extract_element into user", 1);
}
}
CombineResult::no_change()
}
fn combine_subvector_to_shuffle(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode == DAGOpcode::ExtractSubvector {
self.stats.shuffle_folds += 1;
return CombineResult::modified("extract_subvector + insert_subvector → shuffle", 2);
}
CombineResult::no_change()
}
fn combine_bitcast_chain(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.operands.len() < 1 {
return CombineResult::no_change();
}
let src_id = node.operands[0];
if let Some(src_node) = self.dag.get_node(src_id) {
if src_node.opcode == DAGOpcode::BitCast {
if src_node.operands.len() >= 1 {
let inner_id = src_node.operands[0];
if let Some(inner_node) = self.dag.get_node(inner_id) {
if inner_node.value_type == node.value_type {
let opcode = inner_node.opcode;
let operands = inner_node.operands.clone();
let value_type = inner_node.value_type;
self.stats.bitcast_chains += 1;
let replacement = DAGNodeRef::new(
self.dag.allocate_id(),
opcode,
operands,
value_type,
);
return CombineResult::replaced(
replacement,
"cancel nested bitcasts",
2,
);
}
}
}
self.stats.bitcast_chains += 1;
return CombineResult::modified("fold bitcast chain", 1);
}
}
CombineResult::no_change()
}
fn combine_sign_extend_inreg(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.operands.len() < 2 {
return CombineResult::no_change();
}
let _operand_id = node.operands[0];
let mask_id = node.operands[1];
if let Some(mask_node) = self.dag.get_node(mask_id) {
if mask_node.opcode == DAGOpcode::Constant {
self.stats.sign_extend_inreg += 1;
return CombineResult::modified("recognize sign_extend_inreg pattern", 1);
}
}
CombineResult::no_change()
}
fn combine_zero_extend_inreg(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.operands.len() < 2 {
return CombineResult::no_change();
}
if let Some(mask_node) = self.dag.get_node(node.operands[1]) {
if mask_node.opcode == DAGOpcode::Constant {
self.stats.zero_extend_inreg += 1;
return CombineResult::modified("recognize zero_extend_inreg: AND with bitmask", 1);
}
}
CombineResult::no_change()
}
pub fn combine_any_extend(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode == DAGOpcode::ZExt {
if node.operands.len() >= 1 {
let src_id = node.operands[0];
if let Some(src_node) = self.dag.get_node(src_id) {
if src_node.opcode == DAGOpcode::Load {
self.stats.any_extend += 1;
return CombineResult::modified(
"load implicitly zero-extends; drop explicit zext",
1,
);
}
}
}
}
CombineResult::no_change()
}
fn combine_select_to_cmov(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.operands.len() < 3 {
return CombineResult::no_change();
}
let cond_id = node.operands[0];
if let Some(cond_node) = self.dag.get_node(cond_id) {
if cond_node.opcode == DAGOpcode::SetCC || cond_node.opcode == DAGOpcode::X86Cmp {
let cmov = DAGNodeRef::new(
self.dag.allocate_id(),
DAGOpcode::X86Cmov,
vec![cond_id, node.operands[1], node.operands[2]],
node.value_type,
);
self.stats.cmov_patterns += 1;
return CombineResult::replaced(cmov, "select(setcc) → cmov", 1);
}
}
CombineResult::no_change()
}
fn combine_brcond_to_x86_brcond(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.operands.len() < 3 {
return CombineResult::no_change();
}
let cond_id = node.operands[1];
if let Some(cond_node) = self.dag.get_node(cond_id) {
if cond_node.opcode == DAGOpcode::SetCC
|| cond_node.opcode == DAGOpcode::X86Cmp
|| cond_node.opcode == DAGOpcode::ICmp
{
let brcond = DAGNodeRef::new(
self.dag.allocate_id(),
DAGOpcode::X86BrCond,
vec![
node.operands[0], cond_id, node.operands[2], ],
node.value_type,
);
self.stats.brcond_patterns += 1;
return CombineResult::replaced(brcond, "brcond(setcc) → X86ISD::BRCOND", 1);
}
}
CombineResult::no_change()
}
fn combine_cmp_to_setcc_carry(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode == DAGOpcode::X86Cmp {
self.stats.setcc_carry += 1;
return CombineResult::modified("X86Cmp + SetCC candidate → X86ISD::SETCC_CARRY", 1);
}
CombineResult::no_change()
}
fn combine_scalar_moves_to_vector(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode == DAGOpcode::X86Movsd || node.opcode == DAGOpcode::X86Movss {
self.stats.mov_combines += 1;
return CombineResult::modified("consider movsd+movsd → movups", 1);
}
CombineResult::no_change()
}
fn combine_fadd_fsub_to_fmsub(&mut self, node: &DAGNodeRef) -> CombineResult {
if !self.has_fma {
return CombineResult::no_change();
}
if node.operands.len() < 2 {
return CombineResult::no_change();
}
let lhs_id = node.operands[0];
let rhs_id = node.operands[1];
if let Some(rhs_node) = self.dag.get_node(rhs_id) {
if rhs_node.opcode == DAGOpcode::FMul {
if rhs_node.operands.len() >= 2 {
let ops = rhs_node.operands.clone();
let id = self.dag.allocate_id();
let fmsub = DAGNodeRef::new(
id,
DAGOpcode::X86Fmsub,
vec![ops[0], ops[1], lhs_id],
node.value_type,
);
self.stats.fma_patterns += 1;
return CombineResult::replaced(fmsub, "fsub(a, fmul(b,c)) → fmsub(b,c,a)", 2);
}
}
}
CombineResult::no_change()
}
fn combine_fmul_fadd_to_fmadd(&mut self, node: &DAGNodeRef) -> CombineResult {
if !self.has_fma {
return CombineResult::no_change();
}
if node.operands.len() < 2 {
return CombineResult::no_change();
}
for &child_idx in &[0, 1] {
let child_id = node.operands[child_idx];
if let Some(child_node) = self.dag.get_node(child_id) {
if child_node.opcode == DAGOpcode::FMul {
if child_node.operands.len() >= 2 {
let ops = child_node.operands.clone();
let other_id = node.operands[1 - child_idx];
let id = self.dag.allocate_id();
let fmadd = DAGNodeRef::new(
id,
DAGOpcode::X86Fmadd,
vec![ops[0], ops[1], other_id],
node.value_type,
);
self.stats.fma_patterns += 1;
return CombineResult::replaced(
fmadd,
"fadd(fmul(a,b), c) → fmadd(a,b,c)",
2,
);
}
}
}
}
CombineResult::no_change()
}
fn has_undef_operand(&self, node: &DAGNodeRef) -> bool {
node.operands.iter().any(|&id| {
self.dag
.get_node(id)
.map_or(false, |n| n.opcode == DAGOpcode::Undef)
})
}
fn combine_undef_fold(&mut self, node: &DAGNodeRef) -> CombineResult {
let has_undef = node.operands.iter().any(|&id| {
self.dag
.get_node(id)
.map_or(false, |n| n.opcode == DAGOpcode::Undef)
});
if !has_undef {
return CombineResult::no_change();
}
match node.opcode {
DAGOpcode::Add
| DAGOpcode::Sub
| DAGOpcode::Mul
| DAGOpcode::Xor
| DAGOpcode::FAdd
| DAGOpcode::FSub
| DAGOpcode::FMul => {
let undef_node = DAGNodeRef::new(
self.dag.allocate_id(),
DAGOpcode::Undef,
vec![],
node.value_type,
);
self.stats.undef_folds += 1;
CombineResult::replaced(undef_node, "fold undef operand → undef", 1)
}
DAGOpcode::And => {
self.stats.undef_folds += 1;
CombineResult::modified("fold AND with undef", 1)
}
DAGOpcode::Or => {
self.stats.undef_folds += 1;
CombineResult::modified("fold OR with undef", 1)
}
DAGOpcode::Select => {
self.stats.undef_folds += 1;
CombineResult::modified("select with undef condition", 1)
}
_ => CombineResult::no_change(),
}
}
pub fn canonicalize_shuffle(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode != DAGOpcode::ShuffleVector {
return CombineResult::no_change();
}
self.stats.shuffle_canonicalizations += 1;
CombineResult::modified("canonicalize shuffle", 1)
}
pub fn match_address_mode(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode != DAGOpcode::Add {
return CombineResult::no_change();
}
if node.operands.len() < 2 {
return CombineResult::no_change();
}
let _lhs_id = node.operands[0];
let rhs_id = node.operands[1];
let has_constant = self
.dag
.get_node(rhs_id)
.map_or(false, |n| n.opcode == DAGOpcode::Constant);
if has_constant {
self.stats.address_mode_patterns += 1;
return CombineResult::modified("match address mode: base + disp", 1);
}
if let Some(rhs_node) = self.dag.get_node(rhs_id) {
if rhs_node.opcode == DAGOpcode::Mul {
self.stats.address_mode_patterns += 1;
return CombineResult::modified("match address mode: base + idx*scale", 1);
}
}
CombineResult::no_change()
}
pub fn combine_extend_chain(&mut self, node: &DAGNodeRef) -> CombineResult {
match node.opcode {
DAGOpcode::ZExt | DAGOpcode::SExt => {
if node.operands.len() >= 1 {
let src_id = node.operands[0];
if let Some(src_node) = self.dag.get_node(src_id) {
if src_node.opcode == node.opcode {
self.stats.nodes_combined_total += 1;
return CombineResult::modified("fold extend chain", 1);
}
}
}
}
_ => {}
}
CombineResult::no_change()
}
pub fn combine_trunc_of_shift(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode != DAGOpcode::Trunc {
return CombineResult::no_change();
}
if node.operands.len() < 1 {
return CombineResult::no_change();
}
if let Some(src_node) = self.dag.get_node(node.operands[0]) {
if src_node.opcode == DAGOpcode::Shl || src_node.opcode == DAGOpcode::Srl {
return CombineResult::modified("move trunc past shift", 1);
}
}
CombineResult::no_change()
}
pub fn combine_and_to_test(&mut self, node: &DAGNodeRef) -> CombineResult {
if node.opcode == DAGOpcode::And {
if self.dag.use_count(node.id) == 1 {
self.stats.nodes_combined_total += 1;
return CombineResult::modified(
"AND with single use → candidate for TEST folding",
1,
);
}
}
CombineResult::no_change()
}
pub fn is_dead_node(&self, node_id: u32) -> bool {
if self.dag.use_count(node_id) == 0 {
if let Some(node) = self.dag.get_node(node_id) {
let has_side_effects = node.flags.may_store
|| matches!(
node.opcode,
DAGOpcode::Store | DAGOpcode::BrCond | DAGOpcode::TokenFactor
);
return !has_side_effects;
}
}
false
}
pub fn eliminate_dead_nodes(&mut self) -> u32 {
let dead_nodes: Vec<u32> = self
.dag
.nodes
.keys()
.copied()
.filter(|&id| self.is_dead_node(id))
.collect();
let count = dead_nodes.len() as u32;
for id in dead_nodes {
self.dag.delete_node(id);
}
self.stats.nodes_combined_total += count;
count
}
pub fn run_full_combine(&mut self) -> bool {
let mut changed = false;
changed |= self.combine();
let dead = self.eliminate_dead_nodes();
if dead > 0 {
changed = true;
}
let add_node_ids: Vec<u32> = self
.dag
.nodes
.iter()
.filter(|(_, n)| n.opcode == DAGOpcode::Add)
.map(|(id, _)| *id)
.collect();
for id in add_node_ids {
if let Some(node) = self.dag.get_node(id).cloned() {
let result = self.match_address_mode(&node);
if result.action.changed() {
changed = true;
self.stats.nodes_combined_total += 1;
}
}
}
let shuffle_ids: Vec<u32> = self
.dag
.nodes
.iter()
.filter(|(_, n)| n.opcode == DAGOpcode::ShuffleVector)
.map(|(id, _)| *id)
.collect();
for id in shuffle_ids {
if let Some(node) = self.dag.get_node(id).cloned() {
let result = self.canonicalize_shuffle(&node);
if result.action.changed() {
changed = true;
self.stats.nodes_combined_total += 1;
}
}
}
changed
}
pub fn summary(&self) -> String {
format!(
"X86DAGCombineDeep: {} nodes, {} combined in {} iterations. {}",
self.dag.nodes.len(),
self.num_combined,
self.stats.iterations,
self.stats.summary()
)
}
pub fn combined_count(&self) -> u32 {
self.num_combined
}
pub fn reset(&mut self) {
self.num_combined = 0;
self.modified = false;
self.worklist.clear();
self.visited.clear();
self.stats = X86CombineStats::new();
}
}
pub fn make_x86_dag_combine_deep(is_64bit: bool) -> X86DAGCombineDeep {
X86DAGCombineDeep::new(is_64bit)
}
pub fn make_x86_dag_combine_deep_aggressive(is_64bit: bool) -> X86DAGCombineDeep {
X86DAGCombineDeep::new(is_64bit).with_aggressive(true)
}
pub fn make_x86_dag_combine_deep_avx512(is_64bit: bool) -> X86DAGCombineDeep {
X86DAGCombineDeep::new(is_64bit).with_avx512()
}
pub fn make_x86_dag_combine_deep_no_fma(is_64bit: bool) -> X86DAGCombineDeep {
X86DAGCombineDeep::new(is_64bit).without_fma()
}
#[derive(Debug, Clone)]
pub struct CombineBenefit {
pub instructions_saved: i32,
pub cycles_saved: f64,
pub register_pressure_relief: bool,
pub enables_further_combines: bool,
pub description: String,
}
impl CombineBenefit {
pub fn none() -> Self {
Self {
instructions_saved: 0,
cycles_saved: 0.0,
register_pressure_relief: false,
enables_further_combines: false,
description: String::new(),
}
}
pub fn positive(insns: i32, cycles: f64, desc: &str) -> Self {
Self {
instructions_saved: insns,
cycles_saved: cycles,
register_pressure_relief: false,
enables_further_combines: false,
description: desc.to_string(),
}
}
pub fn is_profitable(&self) -> bool {
self.instructions_saved > 0 || self.enables_further_combines
}
}
pub struct CombineCostModel {
pub instruction_cost: f64,
pub register_pressure_penalty: f64,
pub optimize_for_size: bool,
}
impl Default for CombineCostModel {
fn default() -> Self {
Self {
instruction_cost: 1.0,
register_pressure_penalty: 0.2,
optimize_for_size: false,
}
}
}
impl CombineCostModel {
pub fn new() -> Self {
Self::default()
}
pub fn for_size() -> Self {
Self {
optimize_for_size: true,
..Default::default()
}
}
pub fn for_speed() -> Self {
Self {
optimize_for_size: false,
..Default::default()
}
}
pub fn estimate_benefit(&self, old_count: u32, new_count: u32, desc: &str) -> CombineBenefit {
let saved = old_count as i64 - new_count as i64;
let saved_i32 = saved as i32;
let cycles = saved as f64 * self.instruction_cost;
if saved > 0 || (self.optimize_for_size && new_count < old_count) {
CombineBenefit::positive(saved_i32, cycles, desc)
} else {
CombineBenefit::none()
}
}
}
pub struct DAGWalker {
pub order: Vec<u32>,
pub visited: HashSet<u32>,
}
impl DAGWalker {
pub fn new() -> Self {
Self {
order: Vec::new(),
visited: HashSet::new(),
}
}
pub fn compute_order(&mut self, dag: &CombineDAG, root: Option<u32>) -> Vec<u32> {
self.order.clear();
self.visited.clear();
let start = root.unwrap_or_else(|| *dag.nodes.keys().max().unwrap_or(&0));
self.dfs(dag, start);
self.order.reverse(); self.order.clone()
}
fn dfs(&mut self, dag: &CombineDAG, node_id: u32) {
if self.visited.contains(&node_id) {
return;
}
self.visited.insert(node_id);
if let Some(node) = dag.get_node(node_id) {
for &child_id in &node.operands {
self.dfs(dag, child_id);
}
}
self.order.push(node_id);
}
pub fn get_leaf_nodes(&self, dag: &CombineDAG) -> Vec<u32> {
self.order
.iter()
.copied()
.filter(|&id| dag.get_node(id).map_or(true, |n| n.operands.is_empty()))
.collect()
}
pub fn get_root_nodes(&self, dag: &CombineDAG) -> Vec<u32> {
self.order
.iter()
.copied()
.filter(|&id| dag.use_count(id) == 0)
.collect()
}
pub fn depth(&self, dag: &CombineDAG, node_id: u32) -> u32 {
if let Some(node) = dag.get_node(node_id) {
if node.operands.is_empty() {
return 0;
}
node.operands
.iter()
.map(|&child| self.depth(dag, child) + 1)
.max()
.unwrap_or(0)
} else {
0
}
}
pub fn dag_height(&self, dag: &CombineDAG) -> u32 {
self.order
.iter()
.map(|&id| self.depth(dag, id))
.max()
.unwrap_or(0)
}
}
impl Default for DAGWalker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DAGPattern {
pub name: &'static str,
pub root_opcode: DAGOpcode,
pub pattern_type: PatternType,
pub replacement: PatternReplacement,
pub benefit: i32,
pub condition: Option<PatternCondition>,
}
#[derive(Debug, Clone)]
pub enum PatternType {
Single,
Binary,
Unary,
Chain,
Custom(&'static str),
}
#[derive(Debug, Clone)]
pub enum PatternReplacement {
Node(DAGOpcode),
Sequence(Vec<DAGOpcode>),
Delete,
Custom,
}
#[derive(Debug, Clone)]
pub enum PatternCondition {
IsConstant(usize),
IsOpcode(usize, DAGOpcode),
HasOneUse(usize),
SameType(usize, usize),
BitWidth(usize, u32, u32), All(Vec<PatternCondition>),
Any(Vec<PatternCondition>),
}
pub struct X86PatternDatabase {
pub patterns: Vec<DAGPattern>,
}
impl X86PatternDatabase {
pub fn new() -> Self {
Self {
patterns: Vec::new(),
}
}
pub fn register(&mut self, pattern: DAGPattern) {
self.patterns.push(pattern);
}
pub fn build_standard(&mut self) {
self.register(DAGPattern {
name: "bitcast_cancel",
root_opcode: DAGOpcode::BitCast,
pattern_type: PatternType::Chain,
replacement: PatternReplacement::Delete,
benefit: 2,
condition: Some(PatternCondition::IsOpcode(0, DAGOpcode::BitCast)),
});
self.register(DAGPattern {
name: "select_to_cmov",
root_opcode: DAGOpcode::Select,
pattern_type: PatternType::Unary,
replacement: PatternReplacement::Node(DAGOpcode::X86Cmov),
benefit: 1,
condition: Some(PatternCondition::IsOpcode(0, DAGOpcode::SetCC)),
});
self.register(DAGPattern {
name: "fmul_fadd_fmadd",
root_opcode: DAGOpcode::FAdd,
pattern_type: PatternType::Binary,
replacement: PatternReplacement::Node(DAGOpcode::X86Fmadd),
benefit: 2,
condition: Some(PatternCondition::Any(vec![
PatternCondition::IsOpcode(0, DAGOpcode::FMul),
PatternCondition::IsOpcode(1, DAGOpcode::FMul),
])),
});
self.register(DAGPattern {
name: "fmul_fsub_fmsub",
root_opcode: DAGOpcode::FSub,
pattern_type: PatternType::Binary,
replacement: PatternReplacement::Node(DAGOpcode::X86Fmsub),
benefit: 2,
condition: Some(PatternCondition::IsOpcode(1, DAGOpcode::FMul)),
});
self.register(DAGPattern {
name: "brcond_to_x86",
root_opcode: DAGOpcode::BrCond,
pattern_type: PatternType::Unary,
replacement: PatternReplacement::Node(DAGOpcode::X86BrCond),
benefit: 1,
condition: Some(PatternCondition::Any(vec![
PatternCondition::IsOpcode(1, DAGOpcode::SetCC),
PatternCondition::IsOpcode(1, DAGOpcode::X86Cmp),
PatternCondition::IsOpcode(1, DAGOpcode::ICmp),
])),
});
self.register(DAGPattern {
name: "zext_load_fold",
root_opcode: DAGOpcode::ZExt,
pattern_type: PatternType::Unary,
replacement: PatternReplacement::Custom,
benefit: 1,
condition: Some(PatternCondition::IsOpcode(0, DAGOpcode::Load)),
});
self.register(DAGPattern {
name: "sext_load_fold",
root_opcode: DAGOpcode::SExt,
pattern_type: PatternType::Unary,
replacement: PatternReplacement::Custom,
benefit: 1,
condition: Some(PatternCondition::IsOpcode(0, DAGOpcode::Load)),
});
}
pub fn try_match(&self, node: &DAGNodeRef, dag: &CombineDAG) -> Option<(usize, CombineResult)> {
for (i, pattern) in self.patterns.iter().enumerate() {
if node.opcode != pattern.root_opcode {
continue;
}
if let Some(cond) = &pattern.condition {
if !self.check_condition(cond, node, dag) {
continue;
}
}
let result = CombineResult::modified(
&format!("matched pattern: {}", pattern.name),
pattern.benefit,
);
return Some((i, result));
}
None
}
fn check_condition(
&self,
cond: &PatternCondition,
node: &DAGNodeRef,
dag: &CombineDAG,
) -> bool {
match cond {
PatternCondition::IsConstant(idx) => node
.operands
.get(*idx)
.and_then(|&id| dag.get_node(id))
.map_or(false, |n| n.opcode.is_constant()),
PatternCondition::IsOpcode(idx, opcode) => node
.operands
.get(*idx)
.and_then(|&id| dag.get_node(id))
.map_or(false, |n| n.opcode == *opcode),
PatternCondition::HasOneUse(idx) => node
.operands
.get(*idx)
.map_or(false, |&id| dag.use_count(id) <= 1),
PatternCondition::SameType(idx_a, idx_b) => {
let a = node.operands.get(*idx_a).and_then(|&id| dag.get_node(id));
let b = node.operands.get(*idx_b).and_then(|&id| dag.get_node(id));
a.and_then(|a| b.map(|b| a.value_type == b.value_type))
.unwrap_or(false)
}
PatternCondition::BitWidth(idx, min, max) => node
.operands
.get(*idx)
.and_then(|&id| dag.get_node(id))
.map_or(false, |n| n.value_type >= *min && n.value_type <= *max),
PatternCondition::All(conds) => {
conds.iter().all(|c| self.check_condition(c, node, dag))
}
PatternCondition::Any(conds) => {
conds.iter().any(|c| self.check_condition(c, node, dag))
}
}
}
pub fn len(&self) -> usize {
self.patterns.len()
}
pub fn is_empty(&self) -> bool {
self.patterns.is_empty()
}
}
impl Default for X86PatternDatabase {
fn default() -> Self {
let mut db = Self::new();
db.build_standard();
db
}
}
#[derive(Debug, Clone)]
pub struct DAGVerifier {
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl DAGVerifier {
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn verify(&mut self, dag: &CombineDAG) -> bool {
self.errors.clear();
self.warnings.clear();
for (&node_id, node) in &dag.nodes {
for &op_id in &node.operands {
if !dag.nodes.contains_key(&op_id) {
self.errors.push(format!(
"node {node_id} references non-existent operand {op_id}"
));
}
}
}
for (&node_id, node) in &dag.nodes {
if node.operands.contains(&node_id) {
self.errors
.push(format!("node {node_id} references itself"));
}
}
for (&node_id, node) in &dag.nodes {
if (node.flags.may_store || node.opcode == DAGOpcode::Store)
&& dag.use_count(node_id) == 0
&& !matches!(node.opcode, DAGOpcode::EntryToken | DAGOpcode::TokenFactor)
{
self.warnings
.push(format!("node {node_id} has side effects but is unused"));
}
}
let mut computed_uses: HashMap<u32, u32> = HashMap::new();
for (_, node) in &dag.nodes {
for &op_id in &node.operands {
*computed_uses.entry(op_id).or_insert(0) += 1;
}
}
for (&node_id, &computed) in &computed_uses {
let tracked = dag.use_count(node_id);
if computed != tracked {
self.warnings.push(format!(
"use-count mismatch for node {node_id}: computed={computed} tracked={tracked}"
));
}
}
self.errors.is_empty()
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn report(&self) -> String {
let mut r = String::new();
r.push_str(&format!(
"Verification: {} errors, {} warnings\n",
self.errors.len(),
self.warnings.len()
));
for e in &self.errors {
r.push_str(&format!(" ERROR: {e}\n"));
}
for w in &self.warnings {
r.push_str(&format!(" WARN: {w}\n"));
}
r
}
}
impl Default for DAGVerifier {
fn default() -> Self {
Self::new()
}
}
pub struct DAGBuilder {
dag: CombineDAG,
}
impl DAGBuilder {
pub fn new() -> Self {
Self {
dag: CombineDAG::new(),
}
}
pub fn constant(&mut self, value_type: u32) -> u32 {
let id = self.dag.allocate_id();
let node = DAGNodeRef::new(id, DAGOpcode::Constant, vec![], value_type);
self.dag.add_node(node);
id
}
pub fn add(&mut self, lhs: u32, rhs: u32, value_type: u32) -> u32 {
let id = self.dag.allocate_id();
let node = DAGNodeRef::new(id, DAGOpcode::Add, vec![lhs, rhs], value_type);
self.dag.add_node(node);
id
}
pub fn load(&mut self, chain: u32, ptr: u32, value_type: u32) -> u32 {
let id = self.dag.allocate_id();
let mut node = DAGNodeRef::new(id, DAGOpcode::Load, vec![chain, ptr], value_type);
node.flags.may_load = true;
self.dag.add_node(node);
id
}
pub fn store(&mut self, chain: u32, val: u32, ptr: u32, value_type: u32) -> u32 {
let id = self.dag.allocate_id();
let mut node = DAGNodeRef::new(id, DAGOpcode::Store, vec![chain, val, ptr], value_type);
node.flags.may_store = true;
self.dag.add_node(node);
id
}
pub fn bitcast(&mut self, src: u32, value_type: u32) -> u32 {
let id = self.dag.allocate_id();
let node = DAGNodeRef::new(id, DAGOpcode::BitCast, vec![src], value_type);
self.dag.add_node(node);
id
}
pub fn finish(self) -> CombineDAG {
self.dag
}
}
impl Default for DAGBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct DAGStatistics {
pub total_nodes: u32,
pub constant_nodes: u32,
pub load_nodes: u32,
pub store_nodes: u32,
pub arithmetic_nodes: u32,
pub vector_nodes: u32,
pub x86_nodes: u32,
pub max_depth: u32,
pub leaf_count: u32,
pub root_count: u32,
}
impl DAGStatistics {
pub fn collect(dag: &CombineDAG) -> Self {
let mut stats = Self::default();
let mut walker = DAGWalker::new();
let order = walker.compute_order(dag, None);
stats.total_nodes = dag.nodes.len() as u32;
stats.max_depth = walker.dag_height(dag);
stats.leaf_count = walker.get_leaf_nodes(dag).len() as u32;
stats.root_count = walker.get_root_nodes(dag).len() as u32;
for &id in &order {
if let Some(node) = dag.get_node(id) {
match node.opcode {
DAGOpcode::Constant | DAGOpcode::ConstantFP => stats.constant_nodes += 1,
DAGOpcode::Load => stats.load_nodes += 1,
DAGOpcode::Store => stats.store_nodes += 1,
_ if node.opcode.is_vector_op() => stats.vector_nodes += 1,
_ if node.opcode.is_x86_op() => stats.x86_nodes += 1,
_ if !matches!(node.opcode, DAGOpcode::EntryToken | DAGOpcode::TokenFactor) => {
stats.arithmetic_nodes += 1
}
_ => {}
}
}
}
stats
}
pub fn summary(&self) -> String {
format!(
"DAG: {} nodes ({} const, {} ld, {} st, {} arith, {} vec, {} x86), depth={}, leaves={}",
self.total_nodes,
self.constant_nodes,
self.load_nodes,
self.store_nodes,
self.arithmetic_nodes,
self.vector_nodes,
self.x86_nodes,
self.max_depth,
self.leaf_count,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_combiner() -> X86DAGCombineDeep {
X86DAGCombineDeep::new(true)
}
fn make_dag_with_nodes() -> X86DAGCombineDeep {
let mut combiner = X86DAGCombineDeep::new(true);
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32);
combiner.dag.add_node(n2);
combiner
}
#[test]
fn test_combiner_creation() {
let combiner = make_combiner();
assert!(combiner.is_64bit);
assert!(combiner.has_fma);
assert!(!combiner.aggressive);
assert_eq!(combiner.max_iterations, 4);
}
#[test]
fn test_combiner_aggressive() {
let combiner = make_combiner().with_aggressive(true);
assert!(combiner.aggressive);
assert_eq!(combiner.max_iterations, 8);
}
#[test]
fn test_combiner_no_fma() {
let combiner = make_combiner().without_fma();
assert!(!combiner.has_fma);
}
#[test]
fn test_combiner_avx512() {
let combiner = make_combiner().with_avx512();
assert!(combiner.has_avx512);
}
#[test]
fn test_dag_allocate_id() {
let mut dag = CombineDAG::new();
let id0 = dag.allocate_id();
let id1 = dag.allocate_id();
assert_eq!(id0, 0);
assert_eq!(id1, 1);
}
#[test]
fn test_dag_add_and_get_node() {
let mut dag = CombineDAG::new();
let node = DAGNodeRef::new(0, DAGOpcode::Add, vec![1, 2], 32);
dag.add_node(node);
assert!(dag.get_node(0).is_some());
assert_eq!(dag.get_node(0).unwrap().opcode, DAGOpcode::Add);
}
#[test]
fn test_dag_use_count() {
let mut dag = CombineDAG::new();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Add, vec![0, 0], 32);
dag.add_node(n1);
assert_eq!(dag.use_count(0), 2);
}
#[test]
fn test_dag_delete_node() {
let mut dag = CombineDAG::new();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Add, vec![0], 32);
dag.add_node(n1);
assert_eq!(dag.use_count(0), 1);
dag.delete_node(1);
assert_eq!(dag.use_count(0), 0);
assert!(dag.get_node(1).is_none());
}
#[test]
fn test_combine_bitcast_chain() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::BitCast, vec![0], 64);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::BitCast, vec![1], 32);
combiner.dag.add_node(n2);
let result = combiner.combine_node(2);
assert!(result.action.changed());
}
#[test]
fn test_combine_select_to_cmov() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::SetCC, vec![], 1);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n2);
let n3 = DAGNodeRef::new(3, DAGOpcode::Select, vec![0, 1, 2], 32);
combiner.dag.add_node(n3);
let result = combiner.combine_node(3);
assert!(result.action.changed());
assert!(result.description.contains("cmov"));
}
#[test]
fn test_combine_brcond_to_x86_brcond() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::EntryToken, vec![], 0);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::SetCC, vec![], 1);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Constant, vec![], 32); combiner.dag.add_node(n2);
let n3 = DAGNodeRef::new(3, DAGOpcode::BrCond, vec![0, 1, 2], 0);
combiner.dag.add_node(n3);
let result = combiner.combine_node(3);
assert!(result.action.changed());
assert!(result.description.contains("BRCOND"));
}
#[test]
fn test_combine_fmul_fadd_to_fmadd() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::ConstantFP, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::ConstantFP, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::ConstantFP, vec![], 32);
combiner.dag.add_node(n2);
let n3 = DAGNodeRef::new(3, DAGOpcode::FMul, vec![0, 1], 32);
combiner.dag.add_node(n3);
let n4 = DAGNodeRef::new(4, DAGOpcode::FAdd, vec![3, 2], 32);
combiner.dag.add_node(n4);
let result = combiner.combine_node(4);
assert!(result.action.changed());
assert!(result.description.contains("fmadd"));
}
#[test]
fn test_combine_fadd_fsub_to_fmsub() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::ConstantFP, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::ConstantFP, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::ConstantFP, vec![], 32);
combiner.dag.add_node(n2);
let n3 = DAGNodeRef::new(3, DAGOpcode::FMul, vec![0, 1], 32);
combiner.dag.add_node(n3);
let n4 = DAGNodeRef::new(4, DAGOpcode::FSub, vec![2, 3], 32);
combiner.dag.add_node(n4);
let result = combiner.combine_node(4);
assert!(result.action.changed());
assert!(result.description.contains("fmsub"));
}
#[test]
fn test_combine_fma_disabled() {
let mut combiner = make_combiner().without_fma();
let n0 = DAGNodeRef::new(0, DAGOpcode::ConstantFP, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::FMul, vec![0, 0], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::FAdd, vec![1, 0], 32);
combiner.dag.add_node(n2);
let result = combiner.combine_node(2);
assert!(!result.action.changed());
}
#[test]
fn test_combine_undef_fold() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Undef, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32);
combiner.dag.add_node(n2);
let result = combiner.combine_node(2);
assert!(result.action.changed());
}
#[test]
fn test_combine_no_undef() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32);
combiner.dag.add_node(n2);
let result = combiner.combine_node(2);
assert!(!result.action.changed());
}
#[test]
fn test_match_address_mode_base_disp() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::FrameIndex, vec![], 64);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 64);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 64);
combiner.dag.add_node(n2);
let result = combiner.match_address_mode(&combiner.dag.get_node(2).unwrap().clone());
assert!(result.action.changed());
}
#[test]
fn test_match_address_mode_base_index_scale() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::FrameIndex, vec![], 64);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 64); combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Constant, vec![], 64); combiner.dag.add_node(n2);
let n3 = DAGNodeRef::new(3, DAGOpcode::Mul, vec![1, 2], 64);
combiner.dag.add_node(n3);
let n4 = DAGNodeRef::new(4, DAGOpcode::Add, vec![0, 3], 64);
combiner.dag.add_node(n4);
let result = combiner.match_address_mode(&combiner.dag.get_node(4).unwrap().clone());
assert!(result.action.changed());
}
#[test]
fn test_eliminate_dead_nodes() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32);
combiner.dag.add_node(n2);
let dead = combiner.eliminate_dead_nodes();
assert_eq!(dead, 0);
}
#[test]
fn test_dead_node_detection() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
assert!(combiner.is_dead_node(0));
}
#[test]
fn test_combine_load_pair_pattern() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::EntryToken, vec![], 0);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::FrameIndex, vec![], 64);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Constant, vec![], 64);
combiner.dag.add_node(n2);
let n3 = DAGNodeRef::new(3, DAGOpcode::Add, vec![1, 2], 64);
combiner.dag.add_node(n3);
let n4 = DAGNodeRef::new(4, DAGOpcode::Load, vec![0, 3], 32);
combiner.dag.add_node(n4);
let result = combiner.combine_node(4);
assert!(result.action.changed());
}
#[test]
fn test_zero_extend_inreg_recognition() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32); combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::And, vec![0, 1], 32);
combiner.dag.add_node(n2);
let result = combiner.combine_node(2);
assert!(result.action.changed());
}
#[test]
fn test_full_combine_run() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32);
combiner.dag.add_node(n2);
let changed = combiner.combine();
let _ = changed;
}
#[test]
fn test_run_full_combine() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::FrameIndex, vec![], 64);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 64);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 64);
combiner.dag.add_node(n2);
let changed = combiner.run_full_combine();
assert!(changed);
}
#[test]
fn test_stats_new() {
let stats = X86CombineStats::new();
assert_eq!(stats.nodes_combined_total, 0);
}
#[test]
fn test_stats_summary() {
let stats = X86CombineStats::new();
let s = stats.summary();
assert!(s.contains("combined=0"));
}
#[test]
fn test_combiner_summary() {
let combiner = make_combiner();
let s = combiner.summary();
assert!(s.contains("X86DAGCombineDeep"));
}
#[test]
fn test_reset_combiner() {
let mut combiner = make_combiner();
combiner.num_combined = 10;
combiner.modified = true;
combiner.reset();
assert_eq!(combiner.num_combined, 0);
assert!(!combiner.modified);
assert_eq!(combiner.stats.nodes_combined_total, 0);
}
#[test]
fn test_factory_functions() {
let c1 = make_x86_dag_combine_deep(true);
assert!(c1.is_64bit);
let c2 = make_x86_dag_combine_deep_aggressive(true);
assert!(c2.aggressive);
let c3 = make_x86_dag_combine_deep_avx512(false);
assert!(c3.has_avx512);
assert!(!c3.is_64bit);
let c4 = make_x86_dag_combine_deep_no_fma(true);
assert!(!c4.has_fma);
}
#[test]
fn test_opcode_is_load() {
assert!(DAGOpcode::Load.is_load());
assert!(!DAGOpcode::Store.is_load());
}
#[test]
fn test_opcode_is_store() {
assert!(DAGOpcode::Store.is_store());
assert!(!DAGOpcode::Load.is_store());
}
#[test]
fn test_opcode_is_bitcast() {
assert!(DAGOpcode::BitCast.is_bitcast());
assert!(!DAGOpcode::Add.is_bitcast());
}
#[test]
fn test_opcode_is_constant() {
assert!(DAGOpcode::Constant.is_constant());
assert!(DAGOpcode::ConstantFP.is_constant());
assert!(!DAGOpcode::Add.is_constant());
}
#[test]
fn test_opcode_is_undef() {
assert!(DAGOpcode::Undef.is_undef());
assert!(!DAGOpcode::Constant.is_undef());
}
#[test]
fn test_opcode_is_vector_op() {
assert!(DAGOpcode::BuildVector.is_vector_op());
assert!(DAGOpcode::ShuffleVector.is_vector_op());
assert!(!DAGOpcode::Add.is_vector_op());
}
#[test]
fn test_opcode_is_x86_op() {
assert!(DAGOpcode::X86Cmov.is_x86_op());
assert!(DAGOpcode::X86Fmadd.is_x86_op());
assert!(!DAGOpcode::Add.is_x86_op());
}
#[test]
fn test_combine_result_no_change() {
let r = CombineResult::no_change();
assert!(!r.action.changed());
assert_eq!(r.benefit, 0);
}
#[test]
fn test_combine_result_replaced() {
let node = DAGNodeRef::new(99, DAGOpcode::Add, vec![], 32);
let r = CombineResult::replaced(node, "test", 3);
assert!(r.action.changed());
assert_eq!(r.benefit, 3);
assert_eq!(r.description, "test");
}
#[test]
fn test_combine_result_deleted() {
let r = CombineResult::deleted("dead", 2);
assert!(r.action.changed());
assert_eq!(r.benefit, 2);
}
#[test]
fn test_combine_extend_chain_zext_of_zext() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 8);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::ZExt, vec![0], 16);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::ZExt, vec![1], 32);
combiner.dag.add_node(n2);
let result = combiner.combine_extend_chain(&combiner.dag.get_node(2).unwrap().clone());
assert!(result.action.changed());
}
#[test]
fn test_combine_trunc_of_shift() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Shl, vec![0, 0], 32);
combiner.dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Trunc, vec![1], 16);
combiner.dag.add_node(n2);
let result = combiner.combine_trunc_of_shift(&combiner.dag.get_node(2).unwrap().clone());
assert!(result.action.changed());
}
#[test]
fn test_combine_movsd_to_movups() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::X86Movsd, vec![], 64);
combiner.dag.add_node(n0);
let result = combiner.combine_node(0);
assert!(result.action.changed());
}
#[test]
fn test_combine_cmp_to_setcc_carry() {
let mut combiner = make_combiner();
let n0 = DAGNodeRef::new(0, DAGOpcode::X86Cmp, vec![], 0);
combiner.dag.add_node(n0);
let result = combiner.combine_node(0);
assert!(result.action.changed());
}
#[test]
fn test_combine_empty_dag() {
let mut combiner = make_combiner();
let changed = combiner.combine();
assert!(!changed);
assert_eq!(combiner.num_combined, 0);
}
#[test]
fn test_dag_default_creates_empty() {
let dag = CombineDAG::default();
assert!(dag.nodes.is_empty());
assert_eq!(dag.next_id, 0);
}
#[test]
fn test_node_flags_default() {
let flags = NodeFlags::default();
assert!(!flags.has_no_unsigned_wrap);
assert!(!flags.is_volatile);
assert_eq!(flags.alignment, 0);
}
#[test]
fn test_stress_multiple_combines() {
let mut combiner = make_combiner();
for i in 0..20 {
let n = DAGNodeRef::new(i, DAGOpcode::Constant, vec![], 32);
combiner.dag.add_node(n);
}
for i in 20..30 {
let n = DAGNodeRef::new(i, DAGOpcode::Add, vec![i - 10, i - 9], 32);
combiner.dag.add_node(n);
}
let bc1 = DAGNodeRef::new(30, DAGOpcode::BitCast, vec![20], 64);
combiner.dag.add_node(bc1);
let bc2 = DAGNodeRef::new(31, DAGOpcode::BitCast, vec![30], 32);
combiner.dag.add_node(bc2);
let _changed = combiner.combine();
assert!(combiner.stats.nodes_processed > 0);
}
#[test]
fn test_node_creation_and_access() {
let node = DAGNodeRef::new(42, DAGOpcode::Add, vec![1, 2], 32);
assert_eq!(node.id, 42);
assert_eq!(node.opcode, DAGOpcode::Add);
assert_eq!(node.operands.len(), 2);
assert_eq!(node.value_type, 32);
assert!(!node.has_one_use);
}
#[test]
fn test_combine_benefit_positive() {
let b = CombineBenefit::positive(3, 1.5, "test");
assert!(b.is_profitable());
assert_eq!(b.instructions_saved, 3);
assert!((b.cycles_saved - 1.5).abs() < 0.001);
}
#[test]
fn test_combine_benefit_none() {
let b = CombineBenefit::none();
assert!(!b.is_profitable());
assert_eq!(b.instructions_saved, 0);
}
#[test]
fn test_cost_model_estimate() {
let model = CombineCostModel::for_speed();
let benefit = model.estimate_benefit(5, 2, "reduce");
assert!(benefit.is_profitable());
assert_eq!(benefit.instructions_saved, 3);
}
#[test]
fn test_cost_model_no_benefit() {
let model = CombineCostModel::for_speed();
let benefit = model.estimate_benefit(2, 5, "worse");
assert!(!benefit.is_profitable());
}
#[test]
fn test_cost_model_for_size() {
let model = CombineCostModel::for_size();
assert!(model.optimize_for_size);
}
#[test]
fn test_cost_model_default_not_size() {
let model = CombineCostModel::default();
assert!(!model.optimize_for_size);
}
#[test]
fn test_dag_walker_order() {
let mut dag = CombineDAG::new();
let n0 = DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32);
dag.add_node(n0);
let n1 = DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32);
dag.add_node(n1);
let n2 = DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32);
dag.add_node(n2);
let mut walker = DAGWalker::new();
let order = walker.compute_order(&dag, Some(2));
assert_eq!(order.len(), 3);
assert_eq!(*order.last().unwrap(), 2);
}
#[test]
fn test_dag_walker_leaves() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32));
let mut walker = DAGWalker::new();
walker.compute_order(&dag, Some(2));
let leaves = walker.get_leaf_nodes(&dag);
assert_eq!(leaves.len(), 2); assert!(leaves.contains(&0));
assert!(leaves.contains(&1));
}
#[test]
fn test_dag_walker_roots() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Add, vec![0], 32));
let mut walker = DAGWalker::new();
walker.compute_order(&dag, Some(1));
let roots = walker.get_root_nodes(&dag);
assert!(roots.contains(&1));
}
#[test]
fn test_dag_walker_depth() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Neg, vec![0], 32));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Add, vec![1, 0], 32));
let mut walker = DAGWalker::new();
walker.compute_order(&dag, Some(2));
assert_eq!(walker.depth(&dag, 0), 0);
assert_eq!(walker.depth(&dag, 1), 1);
assert_eq!(walker.depth(&dag, 2), 2);
assert_eq!(walker.dag_height(&dag), 2);
}
#[test]
fn test_dag_walker_default() {
let walker = DAGWalker::default();
assert!(walker.order.is_empty());
}
#[test]
fn test_pattern_database_builds() {
let db = X86PatternDatabase::default();
assert!(db.len() > 0);
assert!(!db.is_empty());
}
#[test]
fn test_pattern_database_try_match_bitcast() {
let db = X86PatternDatabase::default();
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::BitCast, vec![0], 64));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::BitCast, vec![1], 32));
let node = dag.get_node(2).unwrap().clone();
let matched = db.try_match(&node, &dag);
assert!(matched.is_some());
}
#[test]
fn test_pattern_database_try_match_select_to_cmov() {
let db = X86PatternDatabase::default();
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::SetCC, vec![], 1));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(3, DAGOpcode::Select, vec![0, 1, 2], 32));
let node = dag.get_node(3).unwrap().clone();
let matched = db.try_match(&node, &dag);
assert!(matched.is_some());
}
#[test]
fn test_pattern_database_no_match_unknown() {
let db = X86PatternDatabase::default();
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Add, vec![], 32));
let node = dag.get_node(0).unwrap().clone();
let matched = db.try_match(&node, &dag);
assert!(matched.is_none());
}
#[test]
fn test_pattern_conditions() {
let db = X86PatternDatabase::default();
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::BitCast, vec![0], 64));
let node = dag.get_node(1).unwrap().clone();
let cond = PatternCondition::IsOpcode(0, DAGOpcode::BitCast);
assert!(!db.check_condition(&cond, &node, &dag));
let cond2 = PatternCondition::IsOpcode(0, DAGOpcode::Constant);
assert!(db.check_condition(&cond2, &node, &dag));
}
#[test]
fn test_pattern_condition_all_any() {
let db = X86PatternDatabase::default();
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::BitCast, vec![0], 64));
let node = dag.get_node(1).unwrap().clone();
let all_cond = PatternCondition::All(vec![
PatternCondition::IsOpcode(0, DAGOpcode::Constant),
PatternCondition::HasOneUse(0),
]);
assert!(db.check_condition(&all_cond, &node, &dag));
let any_cond = PatternCondition::Any(vec![
PatternCondition::IsOpcode(0, DAGOpcode::Add),
PatternCondition::IsOpcode(0, DAGOpcode::Constant),
]);
assert!(db.check_condition(&any_cond, &node, &dag));
}
#[test]
fn test_dag_verifier_valid() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32));
let mut verifier = DAGVerifier::new();
assert!(verifier.verify(&dag));
assert!(!verifier.has_errors());
}
#[test]
fn test_dag_verifier_missing_operand() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Add, vec![99], 32));
let mut verifier = DAGVerifier::new();
assert!(!verifier.verify(&dag));
assert!(verifier.has_errors());
}
#[test]
fn test_dag_verifier_self_reference() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Add, vec![0, 0], 32));
let mut verifier = DAGVerifier::new();
verifier.verify(&dag);
let report = verifier.report();
assert!(report.contains("ERROR") || verifier.has_errors());
}
#[test]
fn test_dag_verifier_report() {
let mut verifier = DAGVerifier::new();
verifier.errors.push("test error".to_string());
verifier.warnings.push("test warning".to_string());
let report = verifier.report();
assert!(report.contains("ERROR"));
assert!(report.contains("WARN"));
}
#[test]
fn test_dag_verifier_default() {
let verifier = DAGVerifier::default();
assert!(verifier.errors.is_empty());
}
#[test]
fn test_dag_builder_create_dag() {
let mut builder = DAGBuilder::new();
let c1 = builder.constant(32);
let c2 = builder.constant(32);
let add = builder.add(c1, c2, 32);
let dag = builder.finish();
assert!(dag.get_node(add).is_some());
assert_eq!(dag.get_node(add).unwrap().opcode, DAGOpcode::Add);
}
#[test]
fn test_dag_builder_load_store() {
let mut builder = DAGBuilder::new();
let chain = builder.constant(0);
let ptr = builder.constant(64);
let load = builder.load(chain, ptr, 32);
let store = builder.store(chain, load, ptr, 32);
let dag = builder.finish();
let load_node = dag.get_node(load).unwrap();
assert!(load_node.flags.may_load);
let store_node = dag.get_node(store).unwrap();
assert!(store_node.flags.may_store);
}
#[test]
fn test_dag_builder_bitcast() {
let mut builder = DAGBuilder::new();
let c = builder.constant(32);
let bc = builder.bitcast(c, 64);
let dag = builder.finish();
let bc_node = dag.get_node(bc).unwrap();
assert_eq!(bc_node.opcode, DAGOpcode::BitCast);
assert_eq!(bc_node.value_type, 64);
}
#[test]
fn test_dag_builder_default() {
let builder = DAGBuilder::default();
let dag = builder.finish();
assert!(dag.nodes.is_empty());
}
#[test]
fn test_dag_statistics_collect() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32));
dag.add_node(DAGNodeRef::new(3, DAGOpcode::Load, vec![2, 0], 32));
let stats = DAGStatistics::collect(&dag);
assert_eq!(stats.total_nodes, 4);
assert_eq!(stats.constant_nodes, 2);
assert_eq!(stats.load_nodes, 1);
assert!(stats.arithmetic_nodes >= 1);
assert!(stats.max_depth > 0);
}
#[test]
fn test_dag_statistics_summary() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
let stats = DAGStatistics::collect(&dag);
let summary = stats.summary();
assert!(summary.contains("DAG:"));
assert!(summary.contains("nodes"));
}
#[test]
fn test_combine_benefit_enables_further() {
let mut b = CombineBenefit::none();
assert!(!b.is_profitable());
b.enables_further_combines = true;
assert!(b.is_profitable());
}
#[test]
fn test_pattern_database_empty() {
let db = X86PatternDatabase::new();
assert!(db.is_empty());
assert_eq!(db.len(), 0);
}
#[test]
fn test_dag_walker_compute_empty_dag() {
let dag = CombineDAG::new();
let mut walker = DAGWalker::new();
let order = walker.compute_order(&dag, None);
assert!(order.is_empty());
assert_eq!(walker.dag_height(&dag), 0);
}
#[test]
fn test_dag_verifier_warns_unused_side_effect() {
let mut dag = CombineDAG::new();
let mut node = DAGNodeRef::new(0, DAGOpcode::Store, vec![], 32);
node.flags.may_store = true;
dag.add_node(node);
let mut verifier = DAGVerifier::new();
verifier.verify(&dag);
let has_warning = verifier.warnings.iter().any(|w| w.contains("unused"));
assert!(has_warning || verifier.warnings.len() >= 1);
}
#[test]
fn test_pattern_database_all_patterns_registered() {
let db = X86PatternDatabase::default();
let names: Vec<&str> = db.patterns.iter().map(|p| p.name).collect();
assert!(names.contains(&"bitcast_cancel"));
assert!(names.contains(&"select_to_cmov"));
assert!(names.contains(&"fmul_fadd_fmadd"));
assert!(names.contains(&"brcond_to_x86"));
assert!(names.contains(&"zext_load_fold"));
assert!(names.contains(&"sext_load_fold"));
}
#[test]
fn test_combine_dead_node_protection() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::Store, vec![], 32));
assert!(!combiner.is_dead_node(0));
}
#[test]
fn test_combine_any_extend_on_zext_of_load() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::EntryToken, vec![], 0));
combiner
.dag
.add_node(DAGNodeRef::new(1, DAGOpcode::FrameIndex, vec![], 64));
combiner
.dag
.add_node(DAGNodeRef::new(2, DAGOpcode::Load, vec![0, 1], 8));
combiner
.dag
.add_node(DAGNodeRef::new(3, DAGOpcode::ZExt, vec![2], 32));
let node = combiner.dag.get_node(3).unwrap().clone();
let result = combiner.combine_any_extend(&node);
assert!(result.action.changed());
}
#[test]
fn test_combine_and_to_test_pattern() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
combiner
.dag
.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
combiner
.dag
.add_node(DAGNodeRef::new(2, DAGOpcode::And, vec![0, 1], 32));
let node = combiner.dag.get_node(2).unwrap().clone();
let result = combiner.combine_and_to_test(&node);
assert!(result.action.changed());
}
#[test]
fn test_combine_trunc_of_shift_pattern() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
combiner
.dag
.add_node(DAGNodeRef::new(1, DAGOpcode::Shl, vec![0, 0], 32));
combiner
.dag
.add_node(DAGNodeRef::new(2, DAGOpcode::Trunc, vec![1], 16));
let node = combiner.dag.get_node(2).unwrap().clone();
let result = combiner.combine_trunc_of_shift(&node);
assert!(result.action.changed());
}
#[test]
fn test_combine_extend_chain() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 8));
combiner
.dag
.add_node(DAGNodeRef::new(1, DAGOpcode::ZExt, vec![0], 16));
combiner
.dag
.add_node(DAGNodeRef::new(2, DAGOpcode::ZExt, vec![1], 32));
let node = combiner.dag.get_node(2).unwrap().clone();
let result = combiner.combine_extend_chain(&node);
assert!(result.action.changed());
}
#[test]
fn test_combine_benefit_with_register_pressure() {
let mut b = CombineBenefit::positive(1, 0.5, "test");
assert!(b.is_profitable());
b.register_pressure_relief = true;
assert!(b.is_profitable());
}
#[test]
fn test_dag_statistics_empty_dag() {
let dag = CombineDAG::new();
let stats = DAGStatistics::collect(&dag);
assert_eq!(stats.total_nodes, 0);
assert_eq!(stats.max_depth, 0);
let summary = stats.summary();
assert!(summary.contains("0 nodes"));
}
#[test]
fn test_dag_verifier_no_issues() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::EntryToken, vec![], 0));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32));
let mut verifier = DAGVerifier::new();
assert!(verifier.verify(&dag));
}
#[test]
fn test_combine_movsd_pattern() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::X86Movsd, vec![], 64));
let result = combiner.combine_node(0);
assert!(result.action.changed());
assert!(combiner.stats.mov_combines > 0);
}
#[test]
fn test_combine_setcc_carry_pattern() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::X86Cmp, vec![], 0));
let result = combiner.combine_node(0);
assert!(result.action.changed());
assert!(combiner.stats.setcc_carry > 0);
}
#[test]
fn test_combine_with_aggressive_iterations() {
let mut combiner = make_combiner().with_aggressive(true);
assert_eq!(combiner.max_iterations, 8);
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
combiner
.dag
.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
combiner
.dag
.add_node(DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32));
let changed = combiner.combine();
let _ = changed;
}
#[test]
fn test_dag_walker_iterator() {
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Add, vec![0, 1], 32));
dag.add_node(DAGNodeRef::new(3, DAGOpcode::Mul, vec![2, 0], 32));
let mut walker = DAGWalker::new();
let order = walker.compute_order(&dag, Some(3));
assert_eq!(order.len(), 4);
let pos_of: HashMap<u32, usize> =
order.iter().enumerate().map(|(i, &id)| (id, i)).collect();
for (&id, node) in &dag.nodes {
for &child in &node.operands {
assert!(
pos_of[&child] < pos_of[&id],
"parent {id} must come after child {child}"
);
}
}
}
#[test]
fn test_pattern_database_match_brcond() {
let db = X86PatternDatabase::default();
let mut dag = CombineDAG::new();
dag.add_node(DAGNodeRef::new(0, DAGOpcode::EntryToken, vec![], 0));
dag.add_node(DAGNodeRef::new(1, DAGOpcode::SetCC, vec![], 1));
dag.add_node(DAGNodeRef::new(2, DAGOpcode::Constant, vec![], 32));
dag.add_node(DAGNodeRef::new(3, DAGOpcode::BrCond, vec![0, 1, 2], 0));
let node = dag.get_node(3).unwrap().clone();
let matched = db.try_match(&node, &dag);
assert!(matched.is_some());
}
#[test]
fn test_cost_model_register_pressure() {
let model = CombineCostModel {
register_pressure_penalty: 0.5,
..CombineCostModel::default()
};
let benefit = model.estimate_benefit(4, 3, "reduce pressure");
assert!(benefit.is_profitable());
}
#[test]
fn test_combiner_reset_clears_stats() {
let mut combiner = make_combiner();
combiner
.dag
.add_node(DAGNodeRef::new(0, DAGOpcode::BitCast, vec![], 32));
combiner
.dag
.add_node(DAGNodeRef::new(1, DAGOpcode::BitCast, vec![0], 32));
combiner
.dag
.add_node(DAGNodeRef::new(2, DAGOpcode::BitCast, vec![1], 32));
combiner.combine();
assert!(combiner.stats.nodes_combined_total > 0);
combiner.reset();
assert_eq!(combiner.stats.nodes_combined_total, 0);
}
#[test]
fn test_dag_builder_complex_graph() {
let mut builder = DAGBuilder::new();
let c1 = builder.constant(64);
let c2 = builder.constant(64);
let add = builder.add(c1, c2, 64);
let load = builder.load(add, c1, 32);
let store = builder.store(load, c2, add, 32);
let dag = builder.finish();
assert!(dag.get_node(store).is_some());
}
#[test]
fn test_all_combine_patterns_stress() {
let mut combiner = make_combiner();
let all_opcodes = [
DAGOpcode::Add,
DAGOpcode::Sub,
DAGOpcode::Mul,
DAGOpcode::And,
DAGOpcode::Or,
DAGOpcode::Xor,
DAGOpcode::BitCast,
DAGOpcode::ZExt,
DAGOpcode::SExt,
DAGOpcode::Select,
DAGOpcode::BrCond,
DAGOpcode::FAdd,
];
let mut prev_id = None;
let mut id_counter = 0u32;
for &opcode in &all_opcodes {
let n = DAGNodeRef::new(
id_counter,
opcode,
prev_id.map(|p| vec![p]).unwrap_or_default(),
32,
);
id_counter += 1;
prev_id = Some(n.id);
combiner.dag.add_node(n);
}
let changed = combiner.run_full_combine();
let _ = changed;
}
#[test]
fn test_combine_result_modified_action() {
let r = CombineResult::modified("modified", 1);
assert!(r.action.changed());
}
#[test]
fn test_dag_node_with_flags() {
let mut node = DAGNodeRef::new(0, DAGOpcode::Load, vec![], 32);
node.flags.may_load = true;
node.flags.alignment = 8;
assert!(node.flags.may_load);
assert_eq!(node.flags.alignment, 8);
assert!(!node.flags.may_store);
}
}