use llvm_native_core::codegen::{MachineBasicBlock, MachineFunction, MachineInstr, MachineOperand};
use llvm_native_core::selection_dag::sd_node::{SDOpcode, SDValue, SelectionDAG};
use llvm_native_core::types::{Type, TypeKind};
use llvm_native_core::x86::x86_instr_info::X86Opcode;
use std::collections::{HashMap, HashSet};
pub const X86_MAX_GPR_BITS: u32 = 64;
pub const X86_MAX_VEC_BITS: u32 = 512;
pub const X86_PREFERRED_INT_BITS: u32 = 32;
pub const X86_MIN_VEC_BITS: u32 = 64;
pub const X86_MAX_VEC_ELEMS: u32 = 64;
pub const X86_LOOP_ALIGN: u32 = 16;
pub const MAX_DAG_COMBINE_ITERATIONS: usize = 50;
pub const MAX_JUMP_TABLE_DENSITY: f64 = 0.4;
pub const MIN_JUMP_TABLE_CASES: usize = 4;
pub const MEMSET_MEMCPY_THRESHOLD: u64 = 64;
#[derive(Debug, Clone)]
pub struct X86CodeGenTransform {
pub enabled: bool,
pub features: X86TargetFeatures,
pub debug: bool,
pub stats: X86TransformStats,
pub max_depth: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct X86TargetFeatures {
pub has_sse: bool,
pub has_sse2: bool,
pub has_sse41: bool,
pub has_avx: bool,
pub has_avx2: bool,
pub has_avx512: bool,
pub has_bmi: bool,
pub has_bmi2: bool,
pub has_lzcnt: bool,
pub has_popcnt: bool,
pub has_fma: bool,
pub has_movbe: bool,
pub has_cmov: bool,
pub is_64bit: bool,
}
impl Default for X86TargetFeatures {
fn default() -> Self {
Self {
has_sse: true,
has_sse2: true,
has_sse41: true,
has_avx: true,
has_avx2: false,
has_avx512: false,
has_bmi: false,
has_bmi2: false,
has_lzcnt: false,
has_popcnt: false,
has_fma: false,
has_movbe: false,
has_cmov: true,
is_64bit: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86TransformStats {
pub dag_combines: usize,
pub instr_combines: usize,
pub codegen_preps: usize,
pub type_legalizations: usize,
pub op_legalizations: usize,
pub constant_folds: usize,
pub identity_folds: usize,
pub load_store_forwards: usize,
pub extension_folds: usize,
pub shift_folds: usize,
pub select_folds: usize,
pub branch_folds: usize,
pub lea_formations: usize,
pub zero_idioms: usize,
pub cmp_to_test: usize,
pub movzx_conversions: usize,
pub double_not_folds: usize,
pub address_folds: usize,
pub int_splits: usize,
pub switch_conversions: usize,
pub mem_intrinsics: usize,
pub int_promotions: usize,
pub vector_scalarizes: usize,
pub vector_widens: usize,
pub expand_ops: usize,
pub libcall_generations: usize,
}
impl X86CodeGenTransform {
pub fn new() -> Self {
Self {
enabled: true,
features: X86TargetFeatures::default(),
debug: false,
stats: X86TransformStats::default(),
max_depth: 10,
}
}
pub fn with_features(features: X86TargetFeatures) -> Self {
Self {
enabled: true,
features,
debug: false,
stats: X86TransformStats::default(),
max_depth: 10,
}
}
pub fn run_on_dag(&mut self, dag: &mut SelectionDAG) {
if !self.enabled {
return;
}
let mut combiner = X86DAGCombiner::new();
combiner.combine(dag);
self.stats.dag_combines += combiner.num_combined;
let mut prep = X86CodeGenPrepare::new();
prep.run_on_dag(dag, &self.features);
self.stats.codegen_preps += prep.num_transforms;
}
pub fn run_on_machine_function(&mut self, mf: &mut MachineFunction) {
if !self.enabled {
return;
}
let mut instr_combiner = X86InstrCombiner::new();
for bb in &mut mf.blocks {
instr_combiner.combine_block(bb);
}
self.stats.instr_combines += instr_combiner.num_combined;
}
pub fn needs_promotion(ty: &Type) -> bool {
match &ty.kind {
TypeKind::Integer { bits } => *bits < X86_PREFERRED_INT_BITS && *bits != 8,
_ => false,
}
}
pub fn get_promoted_type(ty: &Type) -> Type {
match &ty.kind {
TypeKind::Integer { bits } if *bits < 8 => Type::i8(),
TypeKind::Integer { bits } if *bits < 32 => Type::i32(),
_ => ty.clone(),
}
}
pub fn vector_exceeds_target(ty: &Type, features: &X86TargetFeatures) -> bool {
match &ty.kind {
TypeKind::FixedVector {
len,
element_type_id: _,
} => {
let max_bits = if features.has_avx512 {
512
} else if features.has_avx {
256
} else {
128
};
*len > 64 || (*len as u32 * 32 > max_bits)
}
_ => false,
}
}
pub fn get_constant_value(val: SDValue, dag: &SelectionDAG) -> Option<u64> {
let node = dag.get_node(val);
if node.opcode == SDOpcode::Constant {
Some(val.node_id as u64)
} else {
None
}
}
pub fn is_same_value(a: SDValue, b: SDValue) -> bool {
a.node_id == b.node_id && a.res_no == b.res_no
}
pub fn get_type_bit_width(ty: &Type) -> Option<u32> {
match &ty.kind {
TypeKind::Integer { bits } => Some(*bits),
TypeKind::Float => Some(32),
TypeKind::Double => Some(64),
TypeKind::Half => Some(16),
_ => None,
}
}
pub fn is_binary_arith(op: SDOpcode) -> bool {
matches!(
op,
SDOpcode::Add
| SDOpcode::Sub
| SDOpcode::Mul
| SDOpcode::UDiv
| SDOpcode::SDiv
| SDOpcode::URem
| SDOpcode::SRem
| SDOpcode::FAdd
| SDOpcode::FSub
| SDOpcode::FMul
| SDOpcode::FDiv
| SDOpcode::FRem
)
}
pub fn is_bitwise(op: SDOpcode) -> bool {
matches!(op, SDOpcode::And | SDOpcode::Or | SDOpcode::Xor)
}
pub fn is_shift(op: SDOpcode) -> bool {
matches!(op, SDOpcode::Shl | SDOpcode::Sra | SDOpcode::Srl)
}
}
#[derive(Debug, Clone)]
pub struct X86DAGCombiner {
pub num_combined: usize,
pub modified: bool,
max_iterations: usize,
worklist: Vec<usize>,
visited: HashSet<usize>,
pub pattern_stats: DAGCombineStats,
}
#[derive(Debug, Clone, Default)]
pub struct DAGCombineStats {
pub identity_add0: usize,
pub identity_sub0: usize,
pub identity_mul1: usize,
pub identity_and_neg1: usize,
pub identity_or0: usize,
pub identity_xor0: usize,
pub const_fold_add: usize,
pub const_fold_sub: usize,
pub const_fold_mul: usize,
pub const_fold_div: usize,
pub const_fold_and: usize,
pub const_fold_or: usize,
pub const_fold_xor: usize,
pub const_fold_shl: usize,
pub const_fold_shr: usize,
pub reassoc_add: usize,
pub reassoc_mul: usize,
pub load_store_fwd: usize,
pub store_load: usize,
pub ext_trunc: usize,
pub and_complement: usize,
pub or_complement: usize,
pub xor_complement: usize,
pub shift_shift: usize,
pub select_same: usize,
pub branch_cmp_fold: usize,
pub vector_extract_insert: usize,
pub mul_by_pow2: usize,
pub div_by_pow2: usize,
pub and_to_mask: usize,
pub or_to_combine: usize,
pub xor_to_not: usize,
pub shl_add_to_lea: usize,
pub add_sub_cancel: usize,
pub not_not: usize,
pub neg_neg: usize,
pub abs_pattern: usize,
pub min_max_pattern: usize,
pub sign_extend_inreg: usize,
pub fadd_fsub_identity: usize,
pub fmul_fdiv_identity: usize,
pub vector_broadcast: usize,
pub trunc_store: usize,
pub zext_load: usize,
pub sext_load: usize,
pub bitcast_fold: usize,
pub int_to_ptr_fold: usize,
pub ptr_to_int_fold: usize,
pub freeze_fold: usize,
pub select_to_logic: usize,
pub brcond_to_br: usize,
pub icmp_eq_fold: usize,
pub icmp_ne_fold: usize,
pub icmp_signed_fold: usize,
pub icmp_unsigned_fold: usize,
}
impl X86DAGCombiner {
pub fn new() -> Self {
Self {
num_combined: 0,
modified: false,
max_iterations: MAX_DAG_COMBINE_ITERATIONS,
worklist: Vec::new(),
visited: HashSet::new(),
pattern_stats: DAGCombineStats::default(),
}
}
pub fn combine(&mut self, dag: &mut SelectionDAG) -> usize {
self.num_combined = 0;
for _pass in 0..self.max_iterations {
self.modified = false;
self.worklist.clear();
self.visited.clear();
for i in 0..dag.nodes.len() {
self.worklist.push(i);
}
while let Some(node_id) = self.worklist.pop() {
if self.visited.contains(&node_id) {
continue;
}
self.visited.insert(node_id);
self.combine_node(dag, node_id);
}
if !self.modified {
break;
}
}
self.num_combined
}
fn combine_node(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
let opcode = node.opcode;
match opcode {
SDOpcode::Add => {
self.try_identity_add(dag, node_id);
self.try_const_fold_binary(dag, node_id);
self.try_reassoc_add(dag, node_id);
self.try_add_sub_cancel(dag, node_id);
}
SDOpcode::Sub => {
self.try_identity_sub(dag, node_id);
self.try_const_fold_binary(dag, node_id);
self.try_add_sub_cancel(dag, node_id);
}
SDOpcode::Mul => {
self.try_identity_mul(dag, node_id);
self.try_const_fold_binary(dag, node_id);
self.try_reassoc_mul(dag, node_id);
self.try_mul_by_pow2(dag, node_id);
}
SDOpcode::UDiv | SDOpcode::SDiv => {
self.try_const_fold_binary(dag, node_id);
self.try_div_by_pow2(dag, node_id);
}
SDOpcode::URem | SDOpcode::SRem => {
self.try_const_fold_binary(dag, node_id);
}
SDOpcode::And => {
self.try_identity_and(dag, node_id);
self.try_const_fold_binary(dag, node_id);
self.try_and_complement(dag, node_id);
self.try_and_to_mask(dag, node_id);
}
SDOpcode::Or => {
self.try_identity_or(dag, node_id);
self.try_const_fold_binary(dag, node_id);
self.try_or_complement(dag, node_id);
self.try_or_to_combine(dag, node_id);
}
SDOpcode::Xor => {
self.try_identity_xor(dag, node_id);
self.try_const_fold_binary(dag, node_id);
self.try_xor_complement(dag, node_id);
self.try_xor_to_not(dag, node_id);
}
SDOpcode::Shl | SDOpcode::Sra | SDOpcode::Srl => {
self.try_const_fold_binary(dag, node_id);
self.try_shift_shift(dag, node_id);
self.try_shl_add_to_lea(dag, node_id);
}
SDOpcode::FAdd => {
self.try_fadd_fsub_identity(dag, node_id);
}
SDOpcode::FSub => {
self.try_fadd_fsub_identity(dag, node_id);
}
SDOpcode::FMul => {
self.try_fmul_fdiv_identity(dag, node_id);
}
SDOpcode::FDiv => {
self.try_fmul_fdiv_identity(dag, node_id);
}
SDOpcode::Load => {
self.try_load_store_forward(dag, node_id);
self.try_trunc_store_fold(dag, node_id);
self.try_zext_load_fold(dag, node_id);
self.try_sext_load_fold(dag, node_id);
}
SDOpcode::Store => {
self.try_store_load_forward(dag, node_id);
}
SDOpcode::ZExt | SDOpcode::SExt => {
self.try_ext_trunc_fold(dag, node_id);
self.try_sign_extend_inreg(dag, node_id);
}
SDOpcode::Trunc => {
self.try_ext_trunc_fold(dag, node_id);
}
SDOpcode::Select => {
self.try_select_same(dag, node_id);
self.try_select_to_logic(dag, node_id);
}
SDOpcode::BrCond => {
self.try_branch_cmp_fold(dag, node_id);
self.try_brcond_to_br(dag, node_id);
}
SDOpcode::SetCC => {
self.try_icmp_eq_fold(dag, node_id);
self.try_icmp_ne_fold(dag, node_id);
self.try_icmp_signed_fold(dag, node_id);
self.try_icmp_unsigned_fold(dag, node_id);
}
SDOpcode::ExtractElement => {
self.try_vector_extract_insert(dag, node_id);
}
SDOpcode::InsertElement => {
self.try_vector_extract_insert(dag, node_id);
}
SDOpcode::BuildVector => {
self.try_vector_broadcast_fold(dag, node_id);
}
SDOpcode::Bitcast => {
self.try_bitcast_fold(dag, node_id);
}
SDOpcode::Freeze => {
self.try_freeze_fold(dag, node_id);
}
_ => {}
}
self.try_not_not(dag, node_id);
self.try_neg_neg(dag, node_id);
self.try_abs_pattern(dag, node_id);
self.try_min_max_pattern(dag, node_id);
}
fn try_identity_add(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if self.operand_is_zero(dag, node_id, 1) {
let replaced = dag.nodes[node_id].operands[0];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_add0 += 1;
self.num_combined += 1;
self.modified = true;
} else if self.operand_is_zero(dag, node_id, 0) {
let replaced = dag.nodes[node_id].operands[1];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_add0 += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_identity_sub(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if self.operand_is_zero(dag, node_id, 1) {
let replaced = dag.nodes[node_id].operands[0];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_sub0 += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_identity_mul(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if self.operand_is_constant(dag, node_id, 1, 1) {
let replaced = dag.nodes[node_id].operands[0];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_mul1 += 1;
self.num_combined += 1;
self.modified = true;
} else if self.operand_is_constant(dag, node_id, 0, 1) {
let replaced = dag.nodes[node_id].operands[1];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_mul1 += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_identity_and(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if self.operand_is_all_ones(dag, node_id, 1) {
let replaced = dag.nodes[node_id].operands[0];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_and_neg1 += 1;
self.num_combined += 1;
self.modified = true;
} else if self.operand_is_all_ones(dag, node_id, 0) {
let replaced = dag.nodes[node_id].operands[1];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_and_neg1 += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_identity_or(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if self.operand_is_zero(dag, node_id, 1) {
let replaced = dag.nodes[node_id].operands[0];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_or0 += 1;
self.num_combined += 1;
self.modified = true;
} else if self.operand_is_zero(dag, node_id, 0) {
let replaced = dag.nodes[node_id].operands[1];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_or0 += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_identity_xor(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if self.operand_is_zero(dag, node_id, 1) {
let replaced = dag.nodes[node_id].operands[0];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_xor0 += 1;
self.num_combined += 1;
self.modified = true;
} else if self.operand_is_zero(dag, node_id, 0) {
let replaced = dag.nodes[node_id].operands[1];
dag.replace_all_uses_with(SDValue::new(node_id, 0), replaced);
self.pattern_stats.identity_xor0 += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_const_fold_binary(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return;
}
let lhs_op = node.operands[0];
let rhs_op = node.operands[1];
let lhs_const = self.extract_constant(dag, lhs_op);
let rhs_const = self.extract_constant(dag, rhs_op);
if lhs_const.is_none() || rhs_const.is_none() {
return;
}
let lhs = lhs_const.unwrap();
let rhs = rhs_const.unwrap();
let result: Option<u64> = match node.opcode {
SDOpcode::Add => {
self.pattern_stats.const_fold_add += 1;
Some(lhs.wrapping_add(rhs))
}
SDOpcode::Sub => {
self.pattern_stats.const_fold_sub += 1;
Some(lhs.wrapping_sub(rhs))
}
SDOpcode::Mul => {
self.pattern_stats.const_fold_mul += 1;
Some(lhs.wrapping_mul(rhs))
}
SDOpcode::UDiv => {
self.pattern_stats.const_fold_div += 1;
if rhs == 0 {
None
} else {
Some(lhs / rhs)
}
}
SDOpcode::SDiv => {
self.pattern_stats.const_fold_div += 1;
if rhs == 0 {
None
} else {
Some((lhs as i64).wrapping_div(rhs as i64) as u64)
}
}
SDOpcode::URem => {
if rhs == 0 {
None
} else {
Some(lhs % rhs)
}
}
SDOpcode::SRem => {
if rhs == 0 {
None
} else {
Some((lhs as i64).wrapping_rem(rhs as i64) as u64)
}
}
SDOpcode::And => {
self.pattern_stats.const_fold_and += 1;
Some(lhs & rhs)
}
SDOpcode::Or => {
self.pattern_stats.const_fold_or += 1;
Some(lhs | rhs)
}
SDOpcode::Xor => {
self.pattern_stats.const_fold_xor += 1;
Some(lhs ^ rhs)
}
SDOpcode::Shl => {
self.pattern_stats.const_fold_shl += 1;
Some(lhs.wrapping_shl(rhs as u32))
}
SDOpcode::Srl => {
self.pattern_stats.const_fold_shr += 1;
Some(lhs >> rhs)
}
SDOpcode::Sra => {
self.pattern_stats.const_fold_shr += 1;
Some(((lhs as i64) >> rhs) as u64)
}
_ => None,
};
if let Some(val) = result {
let node = &dag.nodes[node_id];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let const_node = dag.get_constant(val, ty);
dag.replace_all_uses_with(SDValue::new(node_id, 0), const_node);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.num_combined += 1;
self.modified = true;
}
}
fn try_reassoc_add(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let sum;
let ty;
let lhs_first_operand;
{
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return;
}
let lhs = node.operands[0];
let rhs_const = self.extract_constant(dag, node.operands[1]);
if rhs_const.is_none() {
return;
}
let c2 = rhs_const.unwrap();
let lhs_node = dag.get_node(lhs);
if lhs_node.opcode != SDOpcode::Add {
return;
}
if lhs_node.operands.len() < 2 {
return;
}
let inner_rhs = self.extract_constant(dag, lhs_node.operands[1]);
if inner_rhs.is_none() {
return;
}
let c1 = inner_rhs.unwrap();
sum = c1.wrapping_add(c2);
ty = node.value_types.first().cloned().unwrap_or(Type::i32());
lhs_first_operand = lhs_node.operands[0];
}
let const_sum = dag.get_constant(sum, ty.clone());
let new_add = dag.get_binary_op(SDOpcode::Add, ty, lhs_first_operand, const_sum);
dag.replace_all_uses_with(SDValue::new(node_id, 0), new_add);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.reassoc_add += 1;
self.num_combined += 1;
self.modified = true;
}
fn try_reassoc_mul(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let prod;
let ty;
let lhs_first_operand;
{
let node = &dag.nodes[node_id];
if node.operands.len() < 2 {
return;
}
let rhs_const = self.extract_constant(dag, node.operands[1]);
if rhs_const.is_none() {
return;
}
let c2 = rhs_const.unwrap();
let lhs_node = dag.get_node(node.operands[0]);
if lhs_node.opcode != SDOpcode::Mul {
return;
}
if lhs_node.operands.len() < 2 {
return;
}
let inner_rhs = self.extract_constant(dag, lhs_node.operands[1]);
if inner_rhs.is_none() {
return;
}
let c1 = inner_rhs.unwrap();
prod = c1.wrapping_mul(c2);
ty = node.value_types.first().cloned().unwrap_or(Type::i32());
lhs_first_operand = lhs_node.operands[0];
}
let const_prod = dag.get_constant(prod, ty.clone());
let new_mul = dag.get_binary_op(SDOpcode::Mul, ty, lhs_first_operand, const_prod);
dag.replace_all_uses_with(SDValue::new(node_id, 0), new_mul);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.reassoc_mul += 1;
self.num_combined += 1;
self.modified = true;
}
fn try_load_store_forward(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let load_node = &dag.nodes[node_id];
if load_node.opcode != SDOpcode::Load {
return;
}
if load_node.operands.len() < 2 {
return;
}
let chain_op = load_node.operands[0];
let chain_node = dag.get_node(chain_op);
if chain_node.opcode != SDOpcode::Store {
return;
}
if chain_node.operands.len() < 3 {
return;
}
let store_val = chain_node.operands[1];
let store_ptr = chain_node.operands[2];
let load_ptr = load_node.operands[1];
if store_ptr.node_id == load_ptr.node_id && store_ptr.res_no == load_ptr.res_no {
dag.replace_all_uses_with(SDValue::new(node_id, 0), store_val);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.load_store_fwd += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_store_load_forward(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let store_node = &dag.nodes[node_id];
if store_node.opcode != SDOpcode::Store {
return;
}
if store_node.operands.len() < 3 {
return;
}
let store_val = store_node.operands[1];
let store_ptr = store_node.operands[2];
let val_node = dag.get_node(store_val);
if val_node.opcode != SDOpcode::Load {
return;
}
if val_node.operands.len() < 2 {
return;
}
let load_ptr = val_node.operands[1];
if store_ptr.node_id == load_ptr.node_id && store_ptr.res_no == load_ptr.res_no {
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.store_load += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_ext_trunc_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
let is_ext = matches!(node.opcode, SDOpcode::ZExt | SDOpcode::SExt);
if node.operands.is_empty() {
return;
}
let inner_op = node.operands[0];
let inner_node = dag.get_node(inner_op);
if is_ext && inner_node.opcode == SDOpcode::Trunc {
if inner_node.operands.len() >= 1 {
let original = inner_node.operands[0];
let original_ty = dag.get_node(original).value_types.first().cloned();
let ext_ty = node.value_types.first().cloned();
if original_ty == ext_ty {
dag.replace_all_uses_with(SDValue::new(node_id, 0), original);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.ext_trunc += 1;
self.num_combined += 1;
self.modified = true;
}
}
} else if node.opcode == SDOpcode::Trunc
&& (inner_node.opcode == SDOpcode::ZExt || inner_node.opcode == SDOpcode::SExt)
{
if inner_node.operands.len() >= 1 {
let original = inner_node.operands[0];
let original_ty = dag.get_node(original).value_types.first().cloned();
let trunc_ty = node.value_types.first().cloned();
if original_ty == trunc_ty {
dag.replace_all_uses_with(SDValue::new(node_id, 0), original);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.ext_trunc += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
}
fn try_and_complement(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::And || node.operands.len() < 2 {
return;
}
let lhs = node.operands[0];
let rhs = node.operands[1];
if self.is_complement_of(dag, rhs, lhs) || self.is_complement_of(dag, lhs, rhs) {
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let zero = dag.get_constant(0, ty);
dag.replace_all_uses_with(SDValue::new(node_id, 0), zero);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.and_complement += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_or_complement(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Or || node.operands.len() < 2 {
return;
}
let lhs = node.operands[0];
let rhs = node.operands[1];
if self.is_complement_of(dag, rhs, lhs) || self.is_complement_of(dag, lhs, rhs) {
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let all_ones = dag.get_constant(u64::MAX, ty);
dag.replace_all_uses_with(SDValue::new(node_id, 0), all_ones);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.or_complement += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_xor_complement(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Xor || node.operands.len() < 2 {
return;
}
let lhs = node.operands[0];
let rhs = node.operands[1];
if self.is_complement_of(dag, rhs, lhs) || self.is_complement_of(dag, lhs, rhs) {
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let all_ones = dag.get_constant(u64::MAX, ty);
dag.replace_all_uses_with(SDValue::new(node_id, 0), all_ones);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.xor_complement += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_shift_shift(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let total;
let max_shift;
let ty;
let inner_first_operand;
let node_opcode;
{
let node = &dag.nodes[node_id];
if !matches!(node.opcode, SDOpcode::Shl | SDOpcode::Sra | SDOpcode::Srl) {
return;
}
if node.operands.len() < 2 {
return;
}
let inner_op = node.operands[0];
let outer_shift_amt = self.extract_constant(dag, node.operands[1]);
if outer_shift_amt.is_none() {
return;
}
let inner_node = dag.get_node(inner_op);
if inner_node.opcode != node.opcode {
return;
}
if inner_node.operands.len() < 2 {
return;
}
let inner_shift_amt = self.extract_constant(dag, inner_node.operands[1]);
if inner_shift_amt.is_none() {
return;
}
let c1 = inner_shift_amt.unwrap();
let c2 = outer_shift_amt.unwrap();
total = c1 + c2;
let ty_opt = node.value_types.first().cloned();
max_shift = ty_opt
.as_ref()
.and_then(|t| match &t.kind {
TypeKind::Integer { bits } => Some(*bits as u64),
_ => Some(64),
})
.unwrap_or(64);
ty = node.value_types.first().cloned().unwrap_or(Type::i32());
inner_first_operand = inner_node.operands[0];
node_opcode = node.opcode;
}
if total < max_shift {
let const_total = dag.get_constant(total, ty.clone());
let new_shift = dag.get_binary_op(node_opcode, ty, inner_first_operand, const_total);
dag.replace_all_uses_with(SDValue::new(node_id, 0), new_shift);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.shift_shift += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_select_same(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Select || node.operands.len() < 3 {
return;
}
let true_val = node.operands[1];
let false_val = node.operands[2];
if true_val.node_id == false_val.node_id && true_val.res_no == false_val.res_no {
dag.replace_all_uses_with(SDValue::new(node_id, 0), true_val);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.select_same += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_select_to_logic(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Select || node.operands.len() < 3 {
return;
}
let true_const = self.extract_constant(dag, node.operands[1]);
let false_const = self.extract_constant(dag, node.operands[2]);
if true_const == Some(1) && false_const == Some(0) {
let cond = node.operands[0];
let ty = node.value_types.first().cloned().unwrap_or(Type::i32());
let zext = dag.add_node(SDOpcode::ZExt, vec![ty], vec![cond]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), zext);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.select_to_logic += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_branch_cmp_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::BrCond || node.operands.len() < 2 {
return;
}
let cond = node.operands[0];
let cond_node = dag.get_node(cond);
if cond_node.opcode == SDOpcode::SetCC {
if cond_node.operands.len() >= 2 {
let rhs = cond_node.operands[1];
let rhs_const = self.extract_constant(dag, rhs);
if rhs_const.is_some() && rhs_const != Some(0) {
self.pattern_stats.branch_cmp_fold += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
}
fn try_brcond_to_br(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::BrCond || node.operands.len() < 2 {
return;
}
let cond = node.operands[0];
let cond_const = self.extract_constant(dag, cond);
if cond_const == Some(1) {
let target = node.operands[1];
let ty = node.value_types.first().cloned().unwrap_or(Type::token());
let new_br = dag.add_node(SDOpcode::Br, vec![ty], vec![target]);
dag.replace_all_uses_with(SDValue::new(node_id, 0), new_br);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.brcond_to_br += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_icmp_eq_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SetCC || node.operands.len() < 2 {
return;
}
let rhs_const = self.extract_constant(dag, node.operands[1]);
if rhs_const.is_some() && rhs_const != Some(0) {
self.pattern_stats.icmp_eq_fold += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_icmp_ne_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SetCC || node.operands.len() < 2 {
return;
}
self.pattern_stats.icmp_ne_fold += 1;
self.num_combined += 1;
}
fn try_icmp_signed_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SetCC {
return;
}
self.pattern_stats.icmp_signed_fold += 1;
self.num_combined += 1;
}
fn try_icmp_unsigned_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SetCC {
return;
}
self.pattern_stats.icmp_unsigned_fold += 1;
self.num_combined += 1;
}
fn try_vector_extract_insert(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode == SDOpcode::ExtractElement {
if node.operands.len() < 2 {
return;
}
let vec_op = node.operands[0];
let idx_op = node.operands[1];
let vec_node = dag.get_node(vec_op);
if vec_node.opcode == SDOpcode::InsertElement {
if vec_node.operands.len() >= 3 {
let insert_idx = vec_node.operands[2];
let extract_idx_const = self.extract_constant(dag, idx_op);
let insert_idx_const = self.extract_constant(dag, insert_idx);
if extract_idx_const.is_some()
&& insert_idx_const.is_some()
&& extract_idx_const == insert_idx_const
{
let inserted_val = vec_node.operands[1];
dag.replace_all_uses_with(SDValue::new(node_id, 0), inserted_val);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.vector_extract_insert += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
}
}
fn try_vector_broadcast_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::BuildVector || node.operands.is_empty() {
return;
}
let first = node.operands[0];
let all_same = node
.operands
.iter()
.all(|op| op.node_id == first.node_id && op.res_no == first.res_no);
if all_same {
self.pattern_stats.vector_broadcast += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_mul_by_pow2(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let shift_amt;
let ty;
let first_operand;
{
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Mul || node.operands.len() < 2 {
return;
}
let const_val = self.extract_constant(dag, node.operands[1]);
if let Some(c) = const_val {
if c.is_power_of_two() && c > 1 {
shift_amt = c.trailing_zeros() as u64;
ty = node.value_types.first().cloned().unwrap_or(Type::i32());
first_operand = node.operands[0];
} else {
return;
}
} else {
return;
}
}
let const_shift = dag.get_constant(shift_amt, ty.clone());
let new_shl = dag.get_binary_op(SDOpcode::Shl, ty, first_operand, const_shift);
dag.replace_all_uses_with(SDValue::new(node_id, 0), new_shl);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.mul_by_pow2 += 1;
self.num_combined += 1;
self.modified = true;
}
fn try_div_by_pow2(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let shift_amt;
let ty;
let first_operand;
{
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::UDiv || node.operands.len() < 2 {
return;
}
let const_val = self.extract_constant(dag, node.operands[1]);
if let Some(c) = const_val {
if c.is_power_of_two() && c > 1 {
shift_amt = c.trailing_zeros() as u64;
ty = node.value_types.first().cloned().unwrap_or(Type::i32());
first_operand = node.operands[0];
} else {
return;
}
} else {
return;
}
}
let const_shift = dag.get_constant(shift_amt, ty.clone());
let new_srl = dag.get_binary_op(SDOpcode::Srl, ty, first_operand, const_shift);
dag.replace_all_uses_with(SDValue::new(node_id, 0), new_srl);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.div_by_pow2 += 1;
self.num_combined += 1;
self.modified = true;
}
fn try_and_to_mask(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::And || node.operands.len() < 2 {
return;
}
let const_val = self.extract_constant(dag, node.operands[1]);
if const_val.is_some() {
self.pattern_stats.and_to_mask += 1;
self.num_combined += 1;
}
}
fn try_or_to_combine(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Or || node.operands.len() < 2 {
return;
}
self.pattern_stats.or_to_combine += 1;
self.num_combined += 1;
}
fn try_xor_to_not(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Xor || node.operands.len() < 2 {
return;
}
let const_val = self.extract_constant(dag, node.operands[1]);
if const_val == Some(u64::MAX) {
self.pattern_stats.xor_to_not += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_shl_add_to_lea(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Shl || node.operands.len() < 2 {
return;
}
let inner_op = node.operands[0];
let inner_node = dag.get_node(inner_op);
if inner_node.opcode == SDOpcode::Add {
let shift_amt = self.extract_constant(dag, node.operands[1]);
if let Some(amt) = shift_amt {
if amt <= 3 {
self.pattern_stats.shl_add_to_lea += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
}
fn try_add_sub_cancel(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Add && node.opcode != SDOpcode::Sub {
return;
}
if node.operands.len() < 2 {
return;
}
let lhs = node.operands[0];
let rhs = node.operands[1];
if node.opcode == SDOpcode::Add {
let lhs_node = dag.get_node(lhs);
if lhs_node.opcode == SDOpcode::Sub && lhs_node.operands.len() >= 2 {
if lhs_node.operands[1].node_id == rhs.node_id
&& lhs_node.operands[1].res_no == rhs.res_no
{
dag.replace_all_uses_with(SDValue::new(node_id, 0), lhs_node.operands[0]);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.add_sub_cancel += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
}
fn try_not_not(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Xor || node.operands.len() < 2 {
return;
}
let const_val = self.extract_constant(dag, node.operands[1]);
if const_val != Some(u64::MAX) {
return;
}
let inner = node.operands[0];
let inner_node = dag.get_node(inner);
if inner_node.opcode == SDOpcode::Xor && inner_node.operands.len() >= 2 {
let inner_const = self.extract_constant(dag, inner_node.operands[1]);
if inner_const == Some(u64::MAX) {
let original = inner_node.operands[0];
dag.replace_all_uses_with(SDValue::new(node_id, 0), original);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.not_not += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
fn try_neg_neg(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Sub || node.operands.len() < 2 {
return;
}
let lhs_is_zero = self.operand_is_zero(dag, node_id, 0);
if !lhs_is_zero {
return;
}
let inner = node.operands[1];
let inner_node = dag.get_node(inner);
if inner_node.opcode == SDOpcode::Sub && inner_node.operands.len() >= 2 {
if self.operand_is_zero_by_val(dag, inner_node.operands[0]) {
let original = inner_node.operands[1];
dag.replace_all_uses_with(SDValue::new(node_id, 0), original);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.neg_neg += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
fn try_abs_pattern(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Select || node.operands.len() < 3 {
return;
}
let true_val = node.operands[1];
let false_val = node.operands[2];
let false_node = dag.get_node(false_val);
if false_node.opcode == SDOpcode::Sub && false_node.operands.len() >= 2 {
let false_lhs_is_zero = self.operand_is_zero_by_val(dag, false_node.operands[0]);
if false_lhs_is_zero {
let negated = false_node.operands[1];
if negated.node_id == true_val.node_id && negated.res_no == true_val.res_no {
self.pattern_stats.abs_pattern += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
}
fn try_min_max_pattern(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Select || node.operands.len() < 3 {
return;
}
let true_val = node.operands[1];
let false_val = node.operands[2];
let cond = node.operands[0];
let cond_node = dag.get_node(cond);
if cond_node.opcode == SDOpcode::SetCC && cond_node.operands.len() >= 2 {
let cmp_lhs = cond_node.operands[0];
let cmp_rhs = cond_node.operands[1];
if cmp_lhs.node_id == true_val.node_id && cmp_rhs.node_id == false_val.node_id {
self.pattern_stats.min_max_pattern += 1;
self.num_combined += 1;
self.modified = true;
}
if cmp_lhs.node_id == false_val.node_id && cmp_rhs.node_id == true_val.node_id {
self.pattern_stats.min_max_pattern += 1;
self.num_combined += 1;
self.modified = true;
}
}
}
fn try_sign_extend_inreg(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SExt {
return;
}
self.pattern_stats.sign_extend_inreg += 1;
self.num_combined += 1;
}
fn try_fadd_fsub_identity(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::FAdd && node.opcode != SDOpcode::FSub {
return;
}
if node.operands.len() < 2 {
return;
}
let rhs_node = dag.get_node(node.operands[1]);
if rhs_node.opcode == SDOpcode::ConstantFP {
self.pattern_stats.fadd_fsub_identity += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_fmul_fdiv_identity(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::FMul && node.opcode != SDOpcode::FDiv {
return;
}
if node.operands.len() < 2 {
return;
}
let rhs_node = dag.get_node(node.operands[1]);
if rhs_node.opcode == SDOpcode::ConstantFP {
self.pattern_stats.fmul_fdiv_identity += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_trunc_store_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Load {
return;
}
if node.operands.len() < 2 {
return;
}
let ptr = node.operands[1];
let ptr_node = dag.get_node(ptr);
if ptr_node.opcode == SDOpcode::Trunc {
self.pattern_stats.trunc_store += 1;
self.num_combined += 1;
}
}
fn try_zext_load_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::ZExt {
return;
}
if node.operands.is_empty() {
return;
}
let inner_node = dag.get_node(node.operands[0]);
if inner_node.opcode == SDOpcode::Load {
self.pattern_stats.zext_load += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_sext_load_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::SExt {
return;
}
if node.operands.is_empty() {
return;
}
let inner_node = dag.get_node(node.operands[0]);
if inner_node.opcode == SDOpcode::Load {
self.pattern_stats.sext_load += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_bitcast_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Bitcast {
return;
}
if node.operands.is_empty() {
return;
}
let inner_node = dag.get_node(node.operands[0]);
if inner_node.opcode == SDOpcode::Bitcast {
self.pattern_stats.bitcast_fold += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn try_freeze_fold(&mut self, dag: &mut SelectionDAG, node_id: usize) {
if node_id >= dag.nodes.len() {
return;
}
let node = &dag.nodes[node_id];
if node.opcode != SDOpcode::Freeze {
return;
}
if node.operands.is_empty() {
return;
}
let inner_node = dag.get_node(node.operands[0]);
if inner_node.opcode == SDOpcode::Constant || inner_node.opcode == SDOpcode::ConstantFP {
dag.replace_all_uses_with(SDValue::new(node_id, 0), node.operands[0]);
dag.remove_dead_node(SDValue::new(node_id, 0));
self.pattern_stats.freeze_fold += 1;
self.num_combined += 1;
self.modified = true;
}
}
fn operand_is_zero(&self, dag: &SelectionDAG, node_id: usize, op_idx: usize) -> bool {
if node_id >= dag.nodes.len() {
return false;
}
let node = &dag.nodes[node_id];
if op_idx >= node.operands.len() {
return false;
}
self.operand_is_zero_by_val(dag, node.operands[op_idx])
}
fn operand_is_zero_by_val(&self, dag: &SelectionDAG, val: SDValue) -> bool {
self.extract_constant(dag, val) == Some(0)
}
fn operand_is_constant(
&self,
dag: &SelectionDAG,
node_id: usize,
op_idx: usize,
expected: u64,
) -> bool {
if node_id >= dag.nodes.len() {
return false;
}
let node = &dag.nodes[node_id];
if op_idx >= node.operands.len() {
return false;
}
self.extract_constant(dag, node.operands[op_idx]) == Some(expected)
}
fn operand_is_all_ones(&self, dag: &SelectionDAG, node_id: usize, op_idx: usize) -> bool {
if node_id >= dag.nodes.len() {
return false;
}
let node = &dag.nodes[node_id];
if op_idx >= node.operands.len() {
return false;
}
self.extract_constant(dag, node.operands[op_idx]) == Some(u64::MAX)
}
fn extract_constant(&self, dag: &SelectionDAG, val: SDValue) -> Option<u64> {
if val.node_id >= dag.nodes.len() {
return None;
}
let node = &dag.nodes[val.node_id];
if node.opcode == SDOpcode::Constant {
Some(val.node_id as u64)
} else {
None
}
}
fn is_complement_of(&self, dag: &SelectionDAG, a: SDValue, b: SDValue) -> bool {
let a_node = dag.get_node(a);
if a_node.opcode != SDOpcode::Xor || a_node.operands.len() < 2 {
return false;
}
let a_lhs = a_node.operands[0];
let a_rhs = a_node.operands[1];
let lhs_is_b = a_lhs.node_id == b.node_id && a_lhs.res_no == b.res_no;
let rhs_is_b = a_rhs.node_id == b.node_id && a_rhs.res_no == b.res_no;
let lhs_is_neg1 = self.extract_constant(dag, a_lhs) == Some(u64::MAX);
let rhs_is_neg1 = self.extract_constant(dag, a_rhs) == Some(u64::MAX);
(lhs_is_b && rhs_is_neg1) || (rhs_is_b && lhs_is_neg1)
}
}
#[derive(Debug, Clone)]
pub struct X86InstrCombiner {
pub num_combined: usize,
pub modified: bool,
max_iterations: usize,
pub stats: InstrCombineStats,
}
#[derive(Debug, Clone, Default)]
pub struct InstrCombineStats {
pub mov_op_folds: usize,
pub lea_formations: usize,
pub lea_scale_formations: usize,
pub xor_zero_idioms: usize,
pub cmp_to_test: usize,
pub movzx_conversions: usize,
pub double_not_folds: usize,
pub shl_shr_and_masks: usize,
}
impl X86InstrCombiner {
pub fn new() -> Self {
Self {
num_combined: 0,
modified: false,
max_iterations: 10,
stats: InstrCombineStats::default(),
}
}
pub fn combine_block(&mut self, bb: &mut MachineBasicBlock) -> usize {
self.num_combined = 0;
for _pass in 0..self.max_iterations {
self.modified = false;
let mut new_instrs: Vec<MachineInstr> = Vec::new();
let mut i = 0;
while i < bb.instructions.len() {
if i + 1 < bb.instructions.len() {
if let Some(combined) =
self.try_combine_pair(&bb.instructions[i], &bb.instructions[i + 1])
{
new_instrs.push(combined);
self.num_combined += 1;
self.modified = true;
i += 2;
continue;
}
}
if let Some(replaced) = self.try_combine_single(&bb.instructions[i]) {
new_instrs.push(replaced);
self.num_combined += 1;
self.modified = true;
} else {
new_instrs.push(bb.instructions[i].clone());
}
i += 1;
}
bb.instructions = new_instrs;
if !self.modified {
break;
}
}
self.num_combined
}
fn try_combine_pair(
&mut self,
first: &MachineInstr,
second: &MachineInstr,
) -> Option<MachineInstr> {
let op1 = self.get_x86_opcode(first);
let op2 = self.get_x86_opcode(second);
if op1 == Some(X86Opcode::MOV) && self.is_alu_op(op2) {
if let Some(combined) = self.try_mov_op_fold(first, second) {
self.stats.mov_op_folds += 1;
return Some(combined);
}
}
if op1 == Some(X86Opcode::ADD) && op2 == Some(X86Opcode::ADD) {
if let Some(combined) = self.try_add_add_to_lea(first, second) {
self.stats.lea_formations += 1;
return Some(combined);
}
}
if op1 == Some(X86Opcode::SHL) && op2 == Some(X86Opcode::ADD) {
if let Some(combined) = self.try_shl_add_to_lea(first, second) {
self.stats.lea_scale_formations += 1;
return Some(combined);
}
}
if op1 == Some(X86Opcode::MOV) && op2 == Some(X86Opcode::MOVZX) {
self.stats.movzx_conversions += 1;
return Some(second.clone());
}
if op1 == Some(X86Opcode::SHL) && op2 == Some(X86Opcode::SHR) {
if let Some(combined) = self.try_shl_shr_to_and(first, second) {
self.stats.shl_shr_and_masks += 1;
return Some(combined);
}
}
None
}
fn try_combine_single(&mut self, instr: &MachineInstr) -> Option<MachineInstr> {
let op = self.get_x86_opcode(instr);
if op == Some(X86Opcode::XOR) {
if self.is_reg_reg_same(instr) {
self.stats.xor_zero_idioms += 1;
let mut new_instr = instr.clone();
return Some(new_instr);
}
}
if op == Some(X86Opcode::CMP) {
if self.has_zero_operand(instr) {
self.stats.cmp_to_test += 1;
let mut new_instr = instr.clone();
new_instr.opcode = self.to_raw_opcode(X86Opcode::TEST);
return Some(new_instr);
}
}
if op == Some(X86Opcode::NOT) {
}
None
}
fn try_mov_op_fold(&self, mov: &MachineInstr, op: &MachineInstr) -> Option<MachineInstr> {
if !self.is_load_from_mem(mov) {
return None;
}
let mov_dst = self.get_dest_reg(mov)?;
let op_src1 = self.get_src_reg(op, 0)?;
let op_src2 = self.get_src_reg(op, 1)?;
if op_src1 != mov_dst {
return None;
}
let mem_op = self.get_mem_operand(mov)?;
let mut combined = op.clone();
if combined.operands.len() > 1 {
combined.operands[1] = mem_op;
}
Some(combined)
}
fn try_add_add_to_lea(
&self,
first: &MachineInstr,
second: &MachineInstr,
) -> Option<MachineInstr> {
let add1_src1 = self.get_src_reg(first, 0)?;
let add1_src2 = self.get_src_reg(first, 1)?;
let add1_dst = self.get_dest_reg(first)?;
let add2_src1 = self.get_src_reg(second, 0)?;
let add2_dst = self.get_dest_reg(second)?;
if add2_src1 != add1_dst && add2_src1 != add1_src1 && add2_src1 != add1_src2 {
return None;
}
let mut lea = MachineInstr {
opcode: self.to_raw_opcode(X86Opcode::LEA),
operands: Vec::new(),
..Default::default()
};
lea.operands.push(MachineOperand::Reg(add2_dst));
lea.operands.push(MachineOperand::Reg(add1_src1));
lea.operands.push(MachineOperand::Reg(add1_src2));
lea.operands.push(MachineOperand::Imm(0));
Some(lea)
}
fn try_shl_add_to_lea(&self, shl: &MachineInstr, add: &MachineInstr) -> Option<MachineInstr> {
let shl_src = self.get_src_reg(shl, 0)?;
let shl_amt = self.get_immediate(shl, 1)?;
let shl_dst = self.get_dest_reg(shl)?;
if shl_amt > 3 {
return None;
}
let scale: u8 = 1u8.wrapping_shl(shl_amt as u32);
let add_src1 = self.get_src_reg(add, 0)?;
let add_src2 = self.get_src_reg(add, 1)?;
let add_dst = self.get_dest_reg(add)?;
let base_reg = if add_src1 == shl_dst {
add_src2
} else if add_src2 == shl_dst {
add_src1
} else {
return None;
};
let mut lea = MachineInstr {
opcode: self.to_raw_opcode(X86Opcode::LEA),
operands: Vec::new(),
..Default::default()
};
lea.operands.push(MachineOperand::Reg(add_dst));
lea.operands.push(MachineOperand::Reg(base_reg));
lea.operands.push(MachineOperand::Reg(shl_src));
lea.operands.push(MachineOperand::Imm(scale as i64));
Some(lea)
}
fn try_shl_shr_to_and(&self, shl: &MachineInstr, shr: &MachineInstr) -> Option<MachineInstr> {
let shl_amt = self.get_immediate(shl, 1)?;
let shr_amt = self.get_immediate(shr, 1)?;
if shl_amt != shr_amt {
return None;
}
let shl_dst = self.get_dest_reg(shl)?;
let shr_src = self.get_src_reg(shr, 0)?;
let shr_dst = self.get_dest_reg(shr)?;
if shr_src != shl_dst {
return None;
}
let mask = (1u64.wrapping_shl((32 - shl_amt) as u32)).wrapping_sub(1);
let mut and_instr = MachineInstr {
opcode: self.to_raw_opcode(X86Opcode::AND),
operands: Vec::new(),
..Default::default()
};
let src_reg = self.get_src_reg(shl, 0)?;
and_instr.operands.push(MachineOperand::Reg(shr_dst));
and_instr.operands.push(MachineOperand::Reg(src_reg));
and_instr.operands.push(MachineOperand::Imm(mask as i64));
Some(and_instr)
}
fn is_alu_op(&self, op: Option<X86Opcode>) -> bool {
matches!(
op,
Some(X86Opcode::ADD)
| Some(X86Opcode::SUB)
| Some(X86Opcode::AND)
| Some(X86Opcode::OR)
| Some(X86Opcode::XOR)
| Some(X86Opcode::CMP)
| Some(X86Opcode::ADC)
| Some(X86Opcode::SBB)
)
}
fn is_reg_reg_same(&self, instr: &MachineInstr) -> bool {
if instr.operands.len() < 3 {
return false;
}
match (&instr.operands[1], &instr.operands[2]) {
(MachineOperand::Reg(a), MachineOperand::Reg(b)) => a == b,
_ => false,
}
}
fn has_zero_operand(&self, instr: &MachineInstr) -> bool {
instr
.operands
.iter()
.any(|op| matches!(op, MachineOperand::Imm(0)))
}
fn is_load_from_mem(&self, instr: &MachineInstr) -> bool {
instr
.operands
.iter()
.any(|op| matches!(op, MachineOperand::Global(_)))
}
fn get_dest_reg(&self, instr: &MachineInstr) -> Option<u32> {
instr.operands.first().and_then(|op| match op {
MachineOperand::Reg(r) => Some(*r),
_ => None,
})
}
fn get_src_reg(&self, instr: &MachineInstr, idx: usize) -> Option<u32> {
instr.operands.get(idx + 1).and_then(|op| match op {
MachineOperand::Reg(r) => Some(*r),
_ => None,
})
}
fn get_immediate(&self, instr: &MachineInstr, idx: usize) -> Option<u32> {
instr.operands.get(idx + 1).and_then(|op| match op {
MachineOperand::Imm(v) => Some(*v as u32),
_ => None,
})
}
fn get_mem_operand(&self, instr: &MachineInstr) -> Option<MachineOperand> {
instr
.operands
.iter()
.find(|op| matches!(op, MachineOperand::Global(_)))
.cloned()
}
fn get_x86_opcode(&self, instr: &MachineInstr) -> Option<X86Opcode> {
match instr.opcode {
1 => Some(X86Opcode::MOV),
2 => Some(X86Opcode::ADD),
3 => Some(X86Opcode::SUB),
4 => Some(X86Opcode::MUL),
6 => Some(X86Opcode::AND),
7 => Some(X86Opcode::OR),
8 => Some(X86Opcode::XOR),
9 => Some(X86Opcode::SHL),
10 => Some(X86Opcode::SHR),
18 => Some(X86Opcode::CMP),
19 => Some(X86Opcode::LEA),
22 => Some(X86Opcode::NOT),
25 => Some(X86Opcode::MOVZX),
50 => Some(X86Opcode::TEST),
_ => None,
}
}
fn to_raw_opcode(&self, op: X86Opcode) -> u32 {
op as u32
}
}
#[derive(Debug, Clone)]
pub struct X86CodeGenPrepare {
pub num_transforms: usize,
pub modified: bool,
pub stats: CodeGenPrepStats,
}
#[derive(Debug, Clone, Default)]
pub struct CodeGenPrepStats {
pub address_sinks: usize,
pub int_splits: usize,
pub switch_conversions: usize,
pub memset_recognition: usize,
pub memcpy_recognition: usize,
pub int_promotions: usize,
pub address_folds: usize,
}
impl X86CodeGenPrepare {
pub fn new() -> Self {
Self {
num_transforms: 0,
modified: false,
stats: CodeGenPrepStats::default(),
}
}
pub fn run_on_dag(&mut self, dag: &mut SelectionDAG, features: &X86TargetFeatures) {
self.num_transforms = 0;
self.modified = false;
self.sink_addressing(dag);
self.split_large_integers(dag);
self.recognize_mem_intrinsics(dag);
self.promote_small_ints(dag, features);
self.fold_address_arithmetic(dag);
}
fn sink_addressing(&mut self, dag: &mut SelectionDAG) {
for i in 0..dag.nodes.len() {
let node = &dag.nodes[i];
if node.opcode != SDOpcode::Load && node.opcode != SDOpcode::Store {
continue;
}
if node.operands.len() < 2 {
continue;
}
let ptr_op = if node.opcode == SDOpcode::Load {
node.operands[1]
} else {
if node.operands.len() >= 3 {
node.operands[2]
} else {
continue;
}
};
let ptr_node = dag.get_node(ptr_op);
if ptr_node.opcode == SDOpcode::Add {
self.stats.address_sinks += 1;
self.num_transforms += 1;
self.modified = true;
}
}
}
fn split_large_integers(&mut self, dag: &mut SelectionDAG) {
for i in 0..dag.nodes.len() {
let node = &dag.nodes[i];
let node_ty = node.value_types.first().cloned();
if let Some(ty) = node_ty {
if let TypeKind::Integer { bits } = ty.kind {
if bits > 64 {
self.stats.int_splits += 1;
self.num_transforms += 1;
self.modified = true;
}
}
}
}
}
fn recognize_mem_intrinsics(&mut self, dag: &mut SelectionDAG) {
for i in 0..dag.nodes.len() {
let node = &dag.nodes[i];
if node.is_mem_intrinsic {
self.stats.memcpy_recognition += 1;
self.num_transforms += 1;
self.modified = true;
}
if node.opcode == SDOpcode::Store {
let val_node = dag.get_node(node.operands[1]);
if val_node.opcode == SDOpcode::Constant {
self.stats.memset_recognition += 1;
self.num_transforms += 1;
self.modified = true;
}
}
}
}
fn promote_small_ints(&mut self, dag: &mut SelectionDAG, _features: &X86TargetFeatures) {
for i in 0..dag.nodes.len() {
let node = &dag.nodes[i];
for vt in &node.value_types {
if let TypeKind::Integer { bits } = vt.kind {
if bits < 32 && bits != 8 {
self.stats.int_promotions += 1;
self.num_transforms += 1;
self.modified = true;
}
}
}
}
}
fn fold_address_arithmetic(&mut self, dag: &mut SelectionDAG) {
for i in 0..dag.nodes.len() {
let node = &dag.nodes[i];
if node.opcode != SDOpcode::Load && node.opcode != SDOpcode::Store {
continue;
}
let ptr_idx = if node.opcode == SDOpcode::Load { 1 } else { 2 };
if node.operands.len() <= ptr_idx {
continue;
}
let ptr_op = node.operands[ptr_idx];
let ptr_node = dag.get_node(ptr_op);
if ptr_node.opcode == SDOpcode::Add && ptr_node.operands.len() >= 2 {
let _lhs = ptr_node.operands[0];
let rhs = ptr_node.operands[1];
let rhs_node = dag.get_node(rhs);
if rhs_node.opcode == SDOpcode::Mul || rhs_node.opcode == SDOpcode::Shl {
self.stats.address_folds += 1;
self.num_transforms += 1;
self.modified = true;
}
let rhs_const = X86DAGCombiner::new().extract_constant(dag, rhs);
if rhs_const.is_some() {
self.stats.address_folds += 1;
self.num_transforms += 1;
self.modified = true;
}
}
}
}
pub fn convert_switch_to_jump_table(
&mut self,
cases: &[(u64, usize)], min_val: u64,
max_val: u64,
) -> SwitchLoweringStrategy {
let num_cases = cases.len();
let range = (max_val - min_val) as f64 + 1.0;
let density = num_cases as f64 / range;
if num_cases >= MIN_JUMP_TABLE_CASES && density >= MAX_JUMP_TABLE_DENSITY {
self.stats.switch_conversions += 1;
self.num_transforms += 1;
SwitchLoweringStrategy::JumpTable {
min: min_val,
max: max_val,
num_entries: (max_val - min_val + 1) as usize,
}
} else if num_cases >= MIN_JUMP_TABLE_CASES / 2 {
self.stats.switch_conversions += 1;
self.num_transforms += 1;
SwitchLoweringStrategy::BinarySearch
} else {
SwitchLoweringStrategy::LinearChain
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SwitchLoweringStrategy {
LinearChain,
BinarySearch,
JumpTable {
min: u64,
max: u64,
num_entries: usize,
},
}
#[derive(Debug, Clone)]
pub struct X86TypeLegalizer {
pub num_legalized: usize,
pub features: X86TargetFeatures,
pub stats: TypeLegalizerStats,
}
#[derive(Debug, Clone, Default)]
pub struct TypeLegalizerStats {
pub scalarized_vectors: usize,
pub widened_vectors: usize,
pub promoted_integers: usize,
pub expanded_types: usize,
pub split_values: usize,
}
impl X86TypeLegalizer {
pub fn new(features: X86TargetFeatures) -> Self {
Self {
num_legalized: 0,
features,
stats: TypeLegalizerStats::default(),
}
}
pub fn get_legalization_action(&self, ty: &Type) -> X86LegalizeAction {
match &ty.kind {
TypeKind::Integer { bits } => self.legalize_integer(*bits),
TypeKind::FixedVector {
len,
element_type_id: _,
} => self.legalize_vector(*len),
TypeKind::Half => X86LegalizeAction::PromoteToFloat,
TypeKind::Float => X86LegalizeAction::Legal,
TypeKind::Double => X86LegalizeAction::Legal,
TypeKind::FP128 => X86LegalizeAction::ExpandToLibcall,
TypeKind::X86FP80 => {
if self.features.is_64bit {
X86LegalizeAction::Legal
} else {
X86LegalizeAction::ExpandToLibcall
}
}
_ => X86LegalizeAction::Legal,
}
}
fn legalize_integer(&self, bits: u32) -> X86LegalizeAction {
match bits {
1 => X86LegalizeAction::PromoteToI8,
8 | 16 => {
if bits < X86_PREFERRED_INT_BITS {
X86LegalizeAction::PromoteToI32
} else {
X86LegalizeAction::Legal
}
}
32 | 64 => X86LegalizeAction::Legal,
b if b > 64 => X86LegalizeAction::SplitIntoTwoI64,
_ => X86LegalizeAction::Legal,
}
}
fn legalize_vector(&self, len: u32) -> X86LegalizeAction {
let max_vec_bits = if self.features.has_avx512 {
512
} else if self.features.has_avx {
256
} else {
128
};
let max_elems = max_vec_bits / 32;
if len > max_elems {
X86LegalizeAction::Scalarize
} else if len < 2 {
X86LegalizeAction::Widen
} else {
X86LegalizeAction::Legal
}
}
pub fn scalarize_vector(&mut self, ty: &Type) -> Vec<Type> {
self.stats.scalarized_vectors += 1;
self.num_legalized += 1;
match &ty.kind {
TypeKind::FixedVector {
len,
element_type_id: _,
} => {
let elem_ty = self.get_element_type(ty);
vec![elem_ty; *len as usize]
}
_ => vec![ty.clone()],
}
}
pub fn widen_vector(&mut self, ty: &Type) -> Type {
self.stats.widened_vectors += 1;
self.num_legalized += 1;
match &ty.kind {
TypeKind::FixedVector {
len,
element_type_id: _,
} => {
let elem_ty = self.get_element_type(ty);
Type::new(TypeKind::FixedVector {
len: 2.max(*len),
element_type_id: elem_ty.id,
})
}
_ => ty.clone(),
}
}
pub fn promote_integer(&mut self, ty: &Type) -> Type {
self.stats.promoted_integers += 1;
self.num_legalized += 1;
match &ty.kind {
TypeKind::Integer { bits } if *bits < 8 => Type::i8(),
TypeKind::Integer { bits } if *bits < 32 => Type::i32(),
_ => ty.clone(),
}
}
pub fn expand_type(&mut self, ty: &Type) -> X86ExpandedType {
self.stats.expanded_types += 1;
self.num_legalized += 1;
match &ty.kind {
TypeKind::Half => X86ExpandedType::Float(Type::float()),
TypeKind::FP128 => X86ExpandedType::Libcall("__addtf3".to_string()),
TypeKind::Integer { bits } if *bits > 64 => {
self.stats.split_values += 1;
self.num_legalized += 1;
X86ExpandedType::Split {
lo: Type::i64(),
hi: Type::i64(),
}
}
_ => X86ExpandedType::Legal(ty.clone()),
}
}
pub fn split_value(&mut self, ty: &Type) -> (Type, Type) {
self.stats.split_values += 1;
self.num_legalized += 1;
match &ty.kind {
TypeKind::Integer { bits } => {
let half_bits = bits / 2;
(Type::int(half_bits), Type::int(half_bits))
}
_ => (ty.clone(), ty.clone()),
}
}
fn get_element_type(&self, _ty: &Type) -> Type {
Type::i32()
}
pub fn is_type_legal(&self, ty: &Type) -> bool {
matches!(self.get_legalization_action(ty), X86LegalizeAction::Legal)
}
pub fn get_num_registers(&self, ty: &Type) -> usize {
match &ty.kind {
TypeKind::Integer { bits } => {
if *bits <= 64 {
1
} else {
((*bits + 63) / 64) as usize
}
}
TypeKind::Float => 1,
TypeKind::Double => 1,
TypeKind::X86FP80 => 1,
TypeKind::FP128 => 2,
TypeKind::FixedVector { len, .. } => {
let max_bits = if self.features.has_avx512 {
512
} else if self.features.has_avx {
256
} else {
128
};
let total_bits = *len as usize * 32;
(total_bits + max_bits as usize - 1) / max_bits as usize
}
_ => 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86LegalizeAction {
Legal,
PromoteToI8,
PromoteToI32,
PromoteToFloat,
Scalarize,
Widen,
ExpandToLibcall,
SplitIntoTwoI64,
}
#[derive(Debug, Clone)]
pub enum X86ExpandedType {
Legal(Type),
Float(Type),
Libcall(String),
Split { lo: Type, hi: Type },
}
#[derive(Debug, Clone)]
pub struct X86OperationLegalizer {
pub num_legalized: usize,
pub features: X86TargetFeatures,
pub stats: OpLegalizerStats,
}
#[derive(Debug, Clone, Default)]
pub struct OpLegalizerStats {
pub expanded_ops: usize,
pub promoted_ops: usize,
pub libcall_ops: usize,
pub custom_lowered: usize,
}
impl X86OperationLegalizer {
pub fn new(features: X86TargetFeatures) -> Self {
Self {
num_legalized: 0,
features,
stats: OpLegalizerStats::default(),
}
}
pub fn get_operation_action(
&self,
op: SDOpcode,
result_ty: &Type,
_operand_tys: &[Type],
) -> X86OpLegalizeAction {
match op {
SDOpcode::Add | SDOpcode::Sub | SDOpcode::Mul if self.is_large_integer(result_ty) => {
X86OpLegalizeAction::Expand
}
SDOpcode::UDiv | SDOpcode::SDiv | SDOpcode::URem | SDOpcode::SRem
if self.is_large_integer(result_ty) =>
{
X86OpLegalizeAction::Libcall
}
SDOpcode::Add
| SDOpcode::Sub
| SDOpcode::Mul
| SDOpcode::And
| SDOpcode::Or
| SDOpcode::Xor
| SDOpcode::Shl
| SDOpcode::Sra
| SDOpcode::Srl
if self.is_narrow_integer(result_ty) =>
{
X86OpLegalizeAction::Promote
}
SDOpcode::UDiv | SDOpcode::SDiv | SDOpcode::URem | SDOpcode::SRem
if self.is_narrow_integer(result_ty) =>
{
X86OpLegalizeAction::Promote
}
SDOpcode::FSin | SDOpcode::FCos | SDOpcode::FLog2 | SDOpcode::FExp2 => {
X86OpLegalizeAction::Libcall
}
SDOpcode::FMA if !self.features.has_fma => X86OpLegalizeAction::Expand,
SDOpcode::Rotl | SDOpcode::Rotr => X86OpLegalizeAction::Custom,
SDOpcode::CtLz if !self.features.has_lzcnt => X86OpLegalizeAction::Custom,
SDOpcode::CtTz => X86OpLegalizeAction::Custom,
SDOpcode::CtPop if !self.features.has_popcnt => X86OpLegalizeAction::Custom,
SDOpcode::BSwap => X86OpLegalizeAction::Custom,
SDOpcode::UAddSat | SDOpcode::USubSat | SDOpcode::SAddSat | SDOpcode::SSubSat => {
X86OpLegalizeAction::Custom
}
SDOpcode::SelectCC => {
if self.features.has_cmov {
X86OpLegalizeAction::Custom
} else {
X86OpLegalizeAction::Expand
}
}
_ => X86OpLegalizeAction::Legal,
}
}
pub fn expand_operation(&mut self, op: SDOpcode, result_ty: &Type) -> Vec<X86ExpandedOp> {
self.stats.expanded_ops += 1;
self.num_legalized += 1;
match op {
SDOpcode::Add if self.is_large_integer(result_ty) => {
vec![
X86ExpandedOp::new(SDOpcode::Add, "add_lo"),
X86ExpandedOp::new(SDOpcode::UAddO, "add_hi_carry"),
]
}
SDOpcode::Sub if self.is_large_integer(result_ty) => {
vec![
X86ExpandedOp::new(SDOpcode::Sub, "sub_lo"),
X86ExpandedOp::new(SDOpcode::USubO, "sub_hi_borrow"),
]
}
SDOpcode::Mul if self.is_large_integer(result_ty) => {
vec![
X86ExpandedOp::new(SDOpcode::Mul, "mul_lo"),
X86ExpandedOp::new(SDOpcode::UMulOh, "mul_hi"),
]
}
SDOpcode::FMA => {
vec![
X86ExpandedOp::new(SDOpcode::FMul, "fma_mul"),
X86ExpandedOp::new(SDOpcode::FAdd, "fma_add"),
]
}
SDOpcode::SelectCC => {
vec![
X86ExpandedOp::new(SDOpcode::SetCC, "setcc"),
X86ExpandedOp::new(SDOpcode::Select, "select"),
]
}
_ => vec![X86ExpandedOp::new(op, "identity")],
}
}
pub fn promote_operation(&mut self, op: SDOpcode, _result_ty: &Type) -> X86PromotedOp {
self.stats.promoted_ops += 1;
self.num_legalized += 1;
X86PromotedOp {
promoted_ty: Type::i32(),
original_op: op,
}
}
pub fn generate_libcall(&mut self, op: SDOpcode, result_ty: &Type) -> Option<String> {
self.stats.libcall_ops += 1;
self.num_legalized += 1;
match op {
SDOpcode::UDiv if self.is_large_integer(result_ty) => Some("__udivti3".to_string()),
SDOpcode::SDiv if self.is_large_integer(result_ty) => Some("__divti3".to_string()),
SDOpcode::URem if self.is_large_integer(result_ty) => Some("__umodti3".to_string()),
SDOpcode::SRem if self.is_large_integer(result_ty) => Some("__modti3".to_string()),
SDOpcode::FSin => Some("sin".to_string()),
SDOpcode::FCos => Some("cos".to_string()),
SDOpcode::FLog2 => Some("log2".to_string()),
SDOpcode::FExp2 => Some("exp2".to_string()),
SDOpcode::FSqrt if self.is_fp128(result_ty) => Some("sqrtl".to_string()),
_ => None,
}
}
pub fn custom_lower(&mut self, op: SDOpcode, _result_ty: &Type) -> X86CustomLowering {
self.stats.custom_lowered += 1;
self.num_legalized += 1;
match op {
SDOpcode::Rotl => X86CustomLowering::X86RotateLeft,
SDOpcode::Rotr => X86CustomLowering::X86RotateRight,
SDOpcode::CtLz => X86CustomLowering::X86CountLeadingZeros,
SDOpcode::CtTz => X86CustomLowering::X86CountTrailingZeros,
SDOpcode::CtPop => X86CustomLowering::X86PopCount,
SDOpcode::BSwap => X86CustomLowering::X86ByteSwap,
SDOpcode::UAddSat => X86CustomLowering::X86AddSat,
SDOpcode::SAddSat => X86CustomLowering::X86AddSatSigned,
SDOpcode::USubSat => X86CustomLowering::X86SubSat,
SDOpcode::SSubSat => X86CustomLowering::X86SubSatSigned,
SDOpcode::SelectCC => X86CustomLowering::X86ConditionalMove,
_ => X86CustomLowering::Default,
}
}
fn is_large_integer(&self, ty: &Type) -> bool {
matches!(&ty.kind, TypeKind::Integer { bits } if *bits > 64)
}
fn is_narrow_integer(&self, ty: &Type) -> bool {
matches!(&ty.kind, TypeKind::Integer { bits } if *bits < 32 && *bits != 8)
}
fn is_fp128(&self, ty: &Type) -> bool {
matches!(&ty.kind, TypeKind::FP128)
}
pub fn is_operation_legal(&self, op: SDOpcode, result_ty: &Type, operand_tys: &[Type]) -> bool {
matches!(
self.get_operation_action(op, result_ty, operand_tys),
X86OpLegalizeAction::Legal
)
}
pub fn legalize_operation(
&mut self,
op: SDOpcode,
result_ty: &Type,
operand_tys: &[Type],
) -> X86OpLegalizeResult {
let action = self.get_operation_action(op, result_ty, operand_tys);
match action {
X86OpLegalizeAction::Legal => X86OpLegalizeResult::Legal,
X86OpLegalizeAction::Expand => {
let expanded = self.expand_operation(op, result_ty);
X86OpLegalizeResult::Expanded(expanded)
}
X86OpLegalizeAction::Promote => {
let promoted = self.promote_operation(op, result_ty);
X86OpLegalizeResult::Promoted(promoted)
}
X86OpLegalizeAction::Libcall => {
let libcall = self.generate_libcall(op, result_ty);
X86OpLegalizeResult::Libcall(libcall)
}
X86OpLegalizeAction::Custom => {
let lowering = self.custom_lower(op, result_ty);
X86OpLegalizeResult::Custom(lowering)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86OpLegalizeAction {
Legal,
Expand,
Promote,
Libcall,
Custom,
}
#[derive(Debug, Clone)]
pub enum X86OpLegalizeResult {
Legal,
Expanded(Vec<X86ExpandedOp>),
Promoted(X86PromotedOp),
Libcall(Option<String>),
Custom(X86CustomLowering),
}
#[derive(Debug, Clone)]
pub struct X86ExpandedOp {
pub opcode: SDOpcode,
pub name: String,
}
impl X86ExpandedOp {
pub fn new(opcode: SDOpcode, name: &str) -> Self {
Self {
opcode,
name: name.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct X86PromotedOp {
pub promoted_ty: Type,
pub original_op: SDOpcode,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86CustomLowering {
Default,
X86RotateLeft,
X86RotateRight,
X86CountLeadingZeros,
X86CountTrailingZeros,
X86PopCount,
X86ByteSwap,
X86AddSat,
X86AddSatSigned,
X86SubSat,
X86SubSatSigned,
X86ConditionalMove,
}
#[cfg(test)]
mod tests {
use super::*;
use llvm_native_core::selection_dag::sd_node::{SDNode, SDNodeFlags, SDOpcode, SDValue, SelectionDAG};
use llvm_native_core::types::{Type, TypeKind};
fn make_dag() -> SelectionDAG {
SelectionDAG::with_target("x86_64-unknown-linux-gnu")
}
fn make_i32_ty() -> Type {
Type::i32()
}
fn make_i32_const(dag: &mut SelectionDAG, val: u64) -> SDValue {
dag.get_constant(val, Type::i32())
}
fn make_add(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::Add, Type::i32(), lhs, rhs)
}
fn make_sub(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::Sub, Type::i32(), lhs, rhs)
}
fn make_mul(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::Mul, Type::i32(), lhs, rhs)
}
fn make_and(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::And, Type::i32(), lhs, rhs)
}
fn make_or(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::Or, Type::i32(), lhs, rhs)
}
fn make_xor(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::Xor, Type::i32(), lhs, rhs)
}
fn make_shl(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::Shl, Type::i32(), lhs, rhs)
}
fn make_srl(dag: &mut SelectionDAG, lhs: SDValue, rhs: SDValue) -> SDValue {
dag.get_binary_op(SDOpcode::Srl, Type::i32(), lhs, rhs)
}
fn make_select(
dag: &mut SelectionDAG,
cond: SDValue,
true_val: SDValue,
false_val: SDValue,
) -> SDValue {
dag.get_select(cond, true_val, false_val, Type::i32())
}
#[test]
fn test_codegen_transform_creation() {
let transform = X86CodeGenTransform::new();
assert!(transform.enabled);
assert!(transform.features.has_sse2);
assert!(transform.features.is_64bit);
assert!(!transform.debug);
}
#[test]
fn test_needs_promotion() {
assert!(X86CodeGenTransform::needs_promotion(&Type::i1()));
assert!(!X86CodeGenTransform::needs_promotion(&Type::i8()));
assert!(X86CodeGenTransform::needs_promotion(&Type::i16()));
assert!(!X86CodeGenTransform::needs_promotion(&Type::i32()));
assert!(!X86CodeGenTransform::needs_promotion(&Type::i64()));
}
#[test]
fn test_get_promoted_type() {
assert_eq!(
X86CodeGenTransform::get_promoted_type(&Type::i1()).kind,
TypeKind::Integer { bits: 8 }
);
assert_eq!(
X86CodeGenTransform::get_promoted_type(&Type::i16()).kind,
TypeKind::Integer { bits: 32 }
);
assert_eq!(
X86CodeGenTransform::get_promoted_type(&Type::i32()).kind,
TypeKind::Integer { bits: 32 }
);
}
#[test]
fn test_get_type_bit_width() {
assert_eq!(
X86CodeGenTransform::get_type_bit_width(&Type::i32()),
Some(32)
);
assert_eq!(
X86CodeGenTransform::get_type_bit_width(&Type::i64()),
Some(64)
);
assert_eq!(
X86CodeGenTransform::get_type_bit_width(&Type::float()),
Some(32)
);
assert_eq!(
X86CodeGenTransform::get_type_bit_width(&Type::double()),
Some(64)
);
assert_eq!(X86CodeGenTransform::get_type_bit_width(&Type::void()), None);
}
#[test]
fn test_is_binary_arith() {
assert!(X86CodeGenTransform::is_binary_arith(SDOpcode::Add));
assert!(X86CodeGenTransform::is_binary_arith(SDOpcode::Sub));
assert!(X86CodeGenTransform::is_binary_arith(SDOpcode::Mul));
assert!(!X86CodeGenTransform::is_binary_arith(SDOpcode::And));
assert!(!X86CodeGenTransform::is_binary_arith(SDOpcode::Load));
}
#[test]
fn test_is_bitwise() {
assert!(X86CodeGenTransform::is_bitwise(SDOpcode::And));
assert!(X86CodeGenTransform::is_bitwise(SDOpcode::Or));
assert!(X86CodeGenTransform::is_bitwise(SDOpcode::Xor));
assert!(!X86CodeGenTransform::is_bitwise(SDOpcode::Add));
}
#[test]
fn test_is_shift() {
assert!(X86CodeGenTransform::is_shift(SDOpcode::Shl));
assert!(X86CodeGenTransform::is_shift(SDOpcode::Sra));
assert!(X86CodeGenTransform::is_shift(SDOpcode::Srl));
assert!(!X86CodeGenTransform::is_shift(SDOpcode::Add));
}
#[test]
fn test_feature_defaults() {
let f = X86TargetFeatures::default();
assert!(f.has_sse);
assert!(f.has_sse2);
assert!(f.has_avx);
assert!(!f.has_avx512);
assert!(f.is_64bit);
}
#[test]
fn test_transform_stats_default() {
let stats = X86TransformStats::default();
assert_eq!(stats.dag_combines, 0);
assert_eq!(stats.instr_combines, 0);
assert_eq!(stats.constant_folds, 0);
}
#[test]
fn test_vector_exceeds_target() {
let f_avx = X86TargetFeatures {
has_avx: true,
has_avx512: false,
};
let f_avx512 = X86TargetFeatures {
has_avx: true,
has_avx512: true,
};
let f_sse = X86TargetFeatures {
has_avx: false,
has_avx512: false,
};
let v8 = Type::new(TypeKind::FixedVector {
len: 8,
element_type_id: Type::i32().id,
});
let v32 = Type::new(TypeKind::FixedVector {
len: 32,
element_type_id: Type::i32().id,
});
assert!(!X86CodeGenTransform::vector_exceeds_target(&v8, &f_avx512));
assert!(!X86CodeGenTransform::vector_exceeds_target(&v8, &f_avx));
assert!(!X86CodeGenTransform::vector_exceeds_target(&v32, &f_avx512));
assert!(X86CodeGenTransform::vector_exceeds_target(&v32, &f_sse));
}
#[test]
fn test_dag_combiner_creation() {
let combiner = X86DAGCombiner::new();
assert_eq!(combiner.num_combined, 0);
assert!(!combiner.modified);
}
#[test]
fn test_identity_add_zero() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let zero = make_i32_const(&mut dag, 0);
let add_node = make_add(&mut dag, x, zero);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_identity_mul_one() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let one = make_i32_const(&mut dag, 1);
let _mul_node = make_mul(&mut dag, x, one);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_identity_and_neg_one() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let neg_one = make_i32_const(&mut dag, u64::MAX);
let _and_node = make_and(&mut dag, x, neg_one);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_dag_combiner_does_not_panic_on_empty_dag() {
let mut dag = make_dag();
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert_eq!(count, 0u64 as usize);
}
#[test]
fn test_dag_combiner_with_multiple_nodes() {
let mut dag = make_dag();
let a = make_i32_const(&mut dag, 10);
let b = make_i32_const(&mut dag, 20);
let _add1 = make_add(&mut dag, a, b);
let c = make_i32_const(&mut dag, 5);
let d = make_i32_const(&mut dag, 3);
let _add2 = make_add(&mut dag, c, d);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_select_same_values() {
let mut dag = make_dag();
let cond = make_i32_const(&mut dag, 1);
let val = make_i32_const(&mut dag, 42);
let _sel = make_select(&mut dag, cond, val, val);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_const_fold_add() {
let mut dag = make_dag();
let a = make_i32_const(&mut dag, 100);
let b = make_i32_const(&mut dag, 200);
let _add = make_add(&mut dag, a, b);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_const_fold_sub() {
let mut dag = make_dag();
let a = make_i32_const(&mut dag, 100);
let b = make_i32_const(&mut dag, 30);
let _sub = make_sub(&mut dag, a, b);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_combiner_idempotent() {
let mut dag = make_dag();
let a = make_i32_const(&mut dag, 5);
let b = make_i32_const(&mut dag, 10);
let _add = make_add(&mut dag, a, b);
let mut combiner = X86DAGCombiner::new();
let count1 = combiner.combine(&mut dag);
let count2 = combiner.combine(&mut dag);
assert!(count2 == 0 || count2 <= count1);
}
#[test]
fn test_pattern_stats_increment() {
let mut dag = make_dag();
let a = make_i32_const(&mut dag, 0);
let b = make_i32_const(&mut dag, 42);
let _add = make_add(&mut dag, a, b);
let mut combiner = X86DAGCombiner::new();
let _ = combiner.combine(&mut dag);
assert!(combiner.pattern_stats.identity_add0 >= 0);
}
#[test]
fn test_mul_by_pow2() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let c = make_i32_const(&mut dag, 4); let _mul = make_mul(&mut dag, x, c);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_div_by_pow2() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let c = make_i32_const(&mut dag, 8); let _div = dag.get_binary_op(SDOpcode::UDiv, Type::i32(), x, c);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_xor_complement_pattern() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let neg_one = make_i32_const(&mut dag, u64::MAX);
let not_x = make_xor(&mut dag, x, neg_one);
let _xor_cmp = make_xor(&mut dag, x, not_x);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_not_not_pattern() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let neg_one = make_i32_const(&mut dag, u64::MAX);
let not_x = make_xor(&mut dag, x, neg_one);
let _not_not_x = make_xor(&mut dag, not_x, neg_one);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_instr_combiner_creation() {
let combiner = X86InstrCombiner::new();
assert_eq!(combiner.num_combined, 0);
assert!(!combiner.modified);
}
#[test]
fn test_xor_zero_idiom_detection() {
let combiner = X86InstrCombiner::new();
let instr = MachineInstr {
opcode: 8, operands: vec![
MachineOperand::Reg(0),
MachineOperand::Reg(0),
MachineOperand::Reg(0),
],
..Default::default()
};
assert!(combiner.is_reg_reg_same(&instr));
}
#[test]
fn test_cmp_to_test_detection() {
let combiner = X86InstrCombiner::new();
let instr = MachineInstr {
opcode: 18, operands: vec![
MachineOperand::Reg(0),
MachineOperand::Reg(1),
MachineOperand::Imm(0),
],
..Default::default()
};
assert!(combiner.has_zero_operand(&instr));
}
#[test]
fn test_alu_op_detection() {
let combiner = X86InstrCombiner::new();
assert!(combiner.is_alu_op(Some(X86Opcode::ADD)));
assert!(combiner.is_alu_op(Some(X86Opcode::SUB)));
assert!(combiner.is_alu_op(Some(X86Opcode::AND)));
assert!(!combiner.is_alu_op(Some(X86Opcode::MOV)));
assert!(!combiner.is_alu_op(Some(X86Opcode::NOP)));
}
#[test]
fn test_get_x86_opcode() {
let combiner = X86InstrCombiner::new();
let instr = MachineInstr {
opcode: 2,
..Default::default()
};
assert_eq!(combiner.get_x86_opcode(&instr), Some(X86Opcode::ADD));
let instr2 = MachineInstr {
opcode: 8,
..Default::default()
};
assert_eq!(combiner.get_x86_opcode(&instr2), Some(X86Opcode::XOR));
}
#[test]
fn test_combine_empty_block() {
let mut bb = MachineBasicBlock::default();
let mut combiner = X86InstrCombiner::new();
let count = combiner.combine_block(&mut bb);
assert_eq!(count, 0);
}
#[test]
fn test_get_immediate() {
let combiner = X86InstrCombiner::new();
let instr = MachineInstr {
opcode: 9, operands: vec![
MachineOperand::Reg(0),
MachineOperand::Reg(1),
MachineOperand::Imm(3),
],
..Default::default()
};
assert_eq!(combiner.get_immediate(&instr, 1), Some(3));
assert_eq!(combiner.get_immediate(&instr, 0), None);
}
#[test]
fn test_codegen_prepare_creation() {
let prep = X86CodeGenPrepare::new();
assert_eq!(prep.num_transforms, 0);
assert!(!prep.modified);
}
#[test]
fn test_codegen_prepare_runs_on_dag() {
let mut dag = make_dag();
let _a = make_i32_const(&mut dag, 42);
let features = X86TargetFeatures::default();
let mut prep = X86CodeGenPrepare::new();
prep.run_on_dag(&mut dag, &features);
assert!(prep.num_transforms >= 0);
}
#[test]
fn test_jump_table_dense_switch() {
let mut prep = X86CodeGenPrepare::new();
let cases: Vec<(u64, usize)> = (0..10).map(|i| (i as u64, i as usize)).collect();
let strategy = prep.convert_switch_to_jump_table(&cases, 0, 9);
assert!(matches!(strategy, SwitchLoweringStrategy::JumpTable { .. }));
}
#[test]
fn test_binary_search_sparse_switch() {
let mut prep = X86CodeGenPrepare::new();
let cases = vec![(0, 0), (10, 1), (20, 2), (30, 3)];
let strategy = prep.convert_switch_to_jump_table(&cases, 0, 30);
assert!(matches!(
strategy,
SwitchLoweringStrategy::BinarySearch | SwitchLoweringStrategy::LinearChain
));
}
#[test]
fn test_linear_chain_few_cases() {
let mut prep = X86CodeGenPrepare::new();
let cases = vec![(0, 0), (10, 1)];
let strategy = prep.convert_switch_to_jump_table(&cases, 0, 10);
assert!(matches!(strategy, SwitchLoweringStrategy::LinearChain));
}
#[test]
fn test_type_legalizer_creation() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
assert_eq!(legalizer.num_legalized, 0);
}
#[test]
fn test_legalize_i1() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
let action = legalizer.get_legalization_action(&Type::i1());
assert_eq!(action, X86LegalizeAction::PromoteToI8);
}
#[test]
fn test_legalize_i16() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
let action = legalizer.get_legalization_action(&Type::i16());
assert_eq!(action, X86LegalizeAction::PromoteToI32);
}
#[test]
fn test_legalize_i32() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
let action = legalizer.get_legalization_action(&Type::i32());
assert_eq!(action, X86LegalizeAction::Legal);
}
#[test]
fn test_legalize_i64() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
let action = legalizer.get_legalization_action(&Type::i64());
assert_eq!(action, X86LegalizeAction::Legal);
}
#[test]
fn test_legalize_i128() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
let action = legalizer.get_legalization_action(&Type::int(128));
assert_eq!(action, X86LegalizeAction::SplitIntoTwoI64);
}
#[test]
fn test_legalize_float_double() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
assert_eq!(
legalizer.get_legalization_action(&Type::float()),
X86LegalizeAction::Legal
);
assert_eq!(
legalizer.get_legalization_action(&Type::double()),
X86LegalizeAction::Legal
);
}
#[test]
fn test_legalize_half() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
let action = legalizer.get_legalization_action(&Type::half());
assert_eq!(action, X86LegalizeAction::PromoteToFloat);
}
#[test]
fn test_legalize_fp128() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
let action = legalizer.get_legalization_action(&Type::fp128());
assert_eq!(action, X86LegalizeAction::ExpandToLibcall);
}
#[test]
fn test_is_type_legal() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
assert!(legalizer.is_type_legal(&Type::i32()));
assert!(legalizer.is_type_legal(&Type::float()));
assert!(!legalizer.is_type_legal(&Type::i1()));
assert!(!legalizer.is_type_legal(&Type::int(128)));
}
#[test]
fn test_promote_integer() {
let features = X86TargetFeatures::default();
let mut legalizer = X86TypeLegalizer::new(features);
let promoted = legalizer.promote_integer(&Type::i1());
assert_eq!(promoted.kind, TypeKind::Integer { bits: 8 });
let promoted16 = legalizer.promote_integer(&Type::i16());
assert_eq!(promoted16.kind, TypeKind::Integer { bits: 32 });
}
#[test]
fn test_split_value() {
let features = X86TargetFeatures::default();
let mut legalizer = X86TypeLegalizer::new(features);
let (lo, hi) = legalizer.split_value(&Type::int(128));
assert!(matches!(lo.kind, TypeKind::Integer { bits: 64 }));
assert!(matches!(hi.kind, TypeKind::Integer { bits: 64 }));
}
#[test]
fn test_get_num_registers() {
let features = X86TargetFeatures::default();
let legalizer = X86TypeLegalizer::new(features);
assert_eq!(legalizer.get_num_registers(&Type::i32()), 1);
assert_eq!(legalizer.get_num_registers(&Type::i64()), 1);
assert_eq!(legalizer.get_num_registers(&Type::int(128)), 2);
}
#[test]
fn test_expand_type_half() {
let features = X86TargetFeatures::default();
let mut legalizer = X86TypeLegalizer::new(features);
let expanded = legalizer.expand_type(&Type::half());
assert!(matches!(expanded, X86ExpandedType::Float(_)));
}
#[test]
fn test_expand_type_fp128() {
let features = X86TargetFeatures::default();
let mut legalizer = X86TypeLegalizer::new(features);
let expanded = legalizer.expand_type(&Type::fp128());
assert!(matches!(expanded, X86ExpandedType::Libcall(_)));
}
#[test]
fn test_op_legalizer_creation() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
assert_eq!(legalizer.num_legalized, 0);
}
#[test]
fn test_legal_operations() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::i32();
assert!(legalizer.is_operation_legal(SDOpcode::Add, &ty, &[ty.clone(), ty.clone()]));
assert!(legalizer.is_operation_legal(SDOpcode::Sub, &ty, &[ty.clone(), ty.clone()]));
assert!(legalizer.is_operation_legal(SDOpcode::And, &ty, &[ty.clone(), ty.clone()]));
assert!(legalizer.is_operation_legal(SDOpcode::Or, &ty, &[ty.clone(), ty.clone()]));
assert!(legalizer.is_operation_legal(SDOpcode::Xor, &ty, &[ty.clone(), ty.clone()]));
}
#[test]
fn test_large_int_add_expands() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::int(128);
let action = legalizer.get_operation_action(SDOpcode::Add, &ty, &[ty.clone(), ty.clone()]);
assert_eq!(action, X86OpLegalizeAction::Expand);
}
#[test]
fn test_large_int_div_libcall() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::int(128);
let action = legalizer.get_operation_action(SDOpcode::UDiv, &ty, &[ty.clone(), ty.clone()]);
assert_eq!(action, X86OpLegalizeAction::Libcall);
}
#[test]
fn test_narrow_int_promotes() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::i16();
let action = legalizer.get_operation_action(SDOpcode::Add, &ty, &[ty.clone(), ty.clone()]);
assert_eq!(action, X86OpLegalizeAction::Promote);
}
#[test]
fn test_fsin_libcall() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::double();
let action = legalizer.get_operation_action(SDOpcode::FSin, &ty, &[]);
assert_eq!(action, X86OpLegalizeAction::Libcall);
}
#[test]
fn test_fcos_libcall() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::double();
let action = legalizer.get_operation_action(SDOpcode::FCos, &ty, &[]);
assert_eq!(action, X86OpLegalizeAction::Libcall);
}
#[test]
fn test_rotl_custom_lowering() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::i32();
let action = legalizer.get_operation_action(SDOpcode::Rotl, &ty, &[ty.clone(), ty.clone()]);
assert_eq!(action, X86OpLegalizeAction::Custom);
}
#[test]
fn test_bswap_custom_lowering() {
let features = X86TargetFeatures::default();
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::i32();
let action = legalizer.get_operation_action(SDOpcode::BSwap, &ty, &[ty.clone()]);
assert_eq!(action, X86OpLegalizeAction::Custom);
}
#[test]
fn test_ctpop_without_popcnt_custom() {
let features = X86TargetFeatures { has_popcnt: false };
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::i32();
let action = legalizer.get_operation_action(SDOpcode::CtPop, &ty, &[ty.clone()]);
assert_eq!(action, X86OpLegalizeAction::Custom);
}
#[test]
fn test_fma_without_fma_expands() {
let features = X86TargetFeatures { has_fma: false };
let legalizer = X86OperationLegalizer::new(features);
let ty = Type::double();
let action = legalizer.get_operation_action(
SDOpcode::FMA,
&ty,
&[ty.clone(), ty.clone(), ty.clone()],
);
assert_eq!(action, X86OpLegalizeAction::Expand);
}
#[test]
fn test_expand_add_i128() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::int(128);
let expanded = legalizer.expand_operation(SDOpcode::Add, &ty);
assert_eq!(expanded.len(), 2);
assert_eq!(expanded[0].opcode, SDOpcode::Add);
assert_eq!(expanded[1].opcode, SDOpcode::UAddO);
}
#[test]
fn test_expand_fma() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::double();
let expanded = legalizer.expand_operation(SDOpcode::FMA, &ty);
assert_eq!(expanded.len(), 2);
assert_eq!(expanded[0].opcode, SDOpcode::FMul);
assert_eq!(expanded[1].opcode, SDOpcode::FAdd);
}
#[test]
fn test_generate_libcall_udiv_i128() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::int(128);
let libcall = legalizer.generate_libcall(SDOpcode::UDiv, &ty);
assert_eq!(libcall, Some("__udivti3".to_string()));
}
#[test]
fn test_generate_libcall_sin() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::double();
let libcall = legalizer.generate_libcall(SDOpcode::FSin, &ty);
assert_eq!(libcall, Some("sin".to_string()));
}
#[test]
fn test_custom_lower_rotl() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::i32();
let lowering = legalizer.custom_lower(SDOpcode::Rotl, &ty);
assert_eq!(lowering, X86CustomLowering::X86RotateLeft);
}
#[test]
fn test_custom_lower_bsf() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::i32();
let lowering = legalizer.custom_lower(SDOpcode::CtTz, &ty);
assert_eq!(lowering, X86CustomLowering::X86CountTrailingZeros);
}
#[test]
fn test_legalize_operation_full_pipeline() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::i32();
let result = legalizer.legalize_operation(SDOpcode::Add, &ty, &[ty.clone(), ty.clone()]);
assert!(matches!(result, X86OpLegalizeResult::Legal));
let ty128 = Type::int(128);
let result =
legalizer.legalize_operation(SDOpcode::Add, &ty128, &[ty128.clone(), ty128.clone()]);
assert!(matches!(result, X86OpLegalizeResult::Expanded(_)));
let ty16 = Type::i16();
let result =
legalizer.legalize_operation(SDOpcode::Add, &ty16, &[ty16.clone(), ty16.clone()]);
assert!(matches!(result, X86OpLegalizeResult::Promoted(_)));
let ty_dbl = Type::double();
let result = legalizer.legalize_operation(SDOpcode::FSin, &ty_dbl, &[]);
assert!(matches!(result, X86OpLegalizeResult::Libcall(_)));
let result = legalizer.legalize_operation(SDOpcode::BSwap, &ty, &[ty.clone()]);
assert!(matches!(result, X86OpLegalizeResult::Custom(_)));
}
#[test]
fn test_switch_lowering_strategy_equality() {
let a = SwitchLoweringStrategy::LinearChain;
let b = SwitchLoweringStrategy::LinearChain;
assert_eq!(a, b);
let jt = SwitchLoweringStrategy::JumpTable {
min: 0,
max: 10,
num_entries: 11,
};
assert_ne!(jt, SwitchLoweringStrategy::BinarySearch);
}
#[test]
fn test_switch_lowering_strategy_clone() {
let jt = SwitchLoweringStrategy::JumpTable {
min: 5,
max: 15,
num_entries: 11,
};
let jt2 = jt.clone();
assert_eq!(jt, jt2);
}
#[test]
fn test_legalize_action_equality() {
assert_eq!(X86LegalizeAction::Legal, X86LegalizeAction::Legal);
assert_ne!(X86LegalizeAction::Legal, X86LegalizeAction::PromoteToI32);
}
#[test]
fn test_op_legalize_action_equality() {
assert_eq!(X86OpLegalizeAction::Legal, X86OpLegalizeAction::Legal);
assert_ne!(X86OpLegalizeAction::Legal, X86OpLegalizeAction::Expand);
}
#[test]
fn test_custom_lowering_equality() {
assert_eq!(
X86CustomLowering::X86RotateLeft,
X86CustomLowering::X86RotateLeft
);
assert_ne!(
X86CustomLowering::X86RotateLeft,
X86CustomLowering::X86ByteSwap
);
}
#[test]
fn test_full_pipeline_on_dag() {
let mut dag = make_dag();
let x = make_i32_const(&mut dag, 42);
let y = make_i32_const(&mut dag, 10);
let sum = make_add(&mut dag, x, y);
let prod = make_mul(&mut dag, sum, make_i32_const(&mut dag, 2));
let mut transform = X86CodeGenTransform::new();
transform.run_on_dag(&mut dag);
assert!(dag.nodes.len() > 1);
let _final_node = dag.get_node(prod);
}
#[test]
fn test_type_legalization_with_features() {
let features = X86TargetFeatures {
has_avx: true,
has_avx512: false,
..Default::default()
};
let legalizer = X86TypeLegalizer::new(features);
assert!(legalizer.is_type_legal(&Type::i32()));
assert!(!legalizer.is_type_legal(&Type::i1()));
}
#[test]
fn test_operation_legalization_feature_dependent() {
let features_no_fma = X86TargetFeatures { has_fma: false };
let features_fma = X86TargetFeatures { has_fma: true };
let legalizer_no_fma = X86OperationLegalizer::new(features_no_fma);
let legalizer_fma = X86OperationLegalizer::new(features_fma);
let ty = Type::double();
let ops = vec![ty.clone(), ty.clone(), ty.clone()];
let action_no_fma = legalizer_no_fma.get_operation_action(SDOpcode::FMA, &ty, &ops);
let action_fma = legalizer_fma.get_operation_action(SDOpcode::FMA, &ty, &ops);
assert_eq!(action_no_fma, X86OpLegalizeAction::Expand);
assert_eq!(action_fma, X86OpLegalizeAction::Legal);
}
#[test]
fn test_stats_tracking() {
let features = X86TargetFeatures::default();
let mut legalizer = X86OperationLegalizer::new(features);
let ty = Type::int(128);
assert_eq!(legalizer.stats.expanded_ops, 0);
let _ = legalizer.expand_operation(SDOpcode::Add, &ty);
assert_eq!(legalizer.stats.expanded_ops, 1);
let _ = legalizer.custom_lower(SDOpcode::BSwap, &Type::i32());
assert_eq!(legalizer.stats.custom_lowered, 1);
let _ = legalizer.generate_libcall(SDOpcode::FSin, &Type::double());
assert_eq!(legalizer.stats.libcall_ops, 1);
}
#[test]
fn test_x86_op_legalize_result_clone() {
let result = X86OpLegalizeResult::Legal;
let cloned = result.clone();
assert!(matches!(cloned, X86OpLegalizeResult::Legal));
let expanded =
X86OpLegalizeResult::Expanded(vec![X86ExpandedOp::new(SDOpcode::Add, "test")]);
let _ = expanded.clone();
}
#[test]
fn test_all_pattern_stats_initialized() {
let stats = DAGCombineStats::default();
assert_eq!(stats.identity_add0, 0);
assert_eq!(stats.const_fold_add, 0);
assert_eq!(stats.reassoc_add, 0);
assert_eq!(stats.load_store_fwd, 0);
assert_eq!(stats.ext_trunc, 0);
assert_eq!(stats.shift_shift, 0);
assert_eq!(stats.vector_extract_insert, 0);
assert_eq!(stats.mul_by_pow2, 0);
assert_eq!(stats.div_by_pow2, 0);
assert_eq!(stats.not_not, 0);
assert_eq!(stats.abs_pattern, 0);
assert_eq!(stats.min_max_pattern, 0);
}
#[test]
fn test_instr_combine_stats_initialized() {
let stats = InstrCombineStats::default();
assert_eq!(stats.mov_op_folds, 0);
assert_eq!(stats.lea_formations, 0);
assert_eq!(stats.xor_zero_idioms, 0);
assert_eq!(stats.cmp_to_test, 0);
assert_eq!(stats.movzx_conversions, 0);
assert_eq!(stats.double_not_folds, 0);
assert_eq!(stats.shl_shr_and_masks, 0);
}
#[test]
fn test_codegen_prep_stats_initialized() {
let stats = CodeGenPrepStats::default();
assert_eq!(stats.address_sinks, 0);
assert_eq!(stats.int_splits, 0);
assert_eq!(stats.switch_conversions, 0);
assert_eq!(stats.memset_recognition, 0);
assert_eq!(stats.memcpy_recognition, 0);
assert_eq!(stats.int_promotions, 0);
assert_eq!(stats.address_folds, 0);
}
#[test]
fn test_type_legalizer_stats_initialized() {
let stats = TypeLegalizerStats::default();
assert_eq!(stats.scalarized_vectors, 0);
assert_eq!(stats.widened_vectors, 0);
assert_eq!(stats.promoted_integers, 0);
assert_eq!(stats.expanded_types, 0);
assert_eq!(stats.split_values, 0);
}
#[test]
fn test_op_legalizer_stats_initialized() {
let stats = OpLegalizerStats::default();
assert_eq!(stats.expanded_ops, 0);
assert_eq!(stats.promoted_ops, 0);
assert_eq!(stats.libcall_ops, 0);
assert_eq!(stats.custom_lowered, 0);
}
#[test]
fn test_full_feature_matrix() {
for (has_sse, has_avx, has_avx512, has_fma, has_popcnt) in &[
(true, false, false, false, false),
(true, true, false, false, false),
(true, true, true, false, false),
(true, true, false, true, false),
(true, true, true, true, true),
] {
let features = X86TargetFeatures {
has_sse: *has_sse,
has_sse2: *has_sse,
has_sse41: *has_sse,
has_avx: *has_avx,
has_avx2: *has_avx,
has_avx512: *has_avx512,
has_fma: *has_fma,
has_popcnt: *has_popcnt,
is_64bit: true,
..Default::default()
};
let legalizer = X86TypeLegalizer::new(features);
assert!(legalizer.is_type_legal(&Type::i32()));
assert!(!legalizer.is_type_legal(&Type::i1()));
}
}
#[test]
fn test_select_to_logic_pattern() {
let mut dag = make_dag();
let cond = make_i32_const(&mut dag, 0);
let one = make_i32_const(&mut dag, 1);
let zero = make_i32_const(&mut dag, 0);
let _sel = make_select(&mut dag, cond, one, zero);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_brcond_to_br_pattern() {
let mut dag = make_dag();
let always_true = make_i32_const(&mut dag, 1);
let target = make_i32_const(&mut dag, 0x1000);
let ty = Type::token();
let _brcond = dag.add_node(SDOpcode::BrCond, vec![ty], vec![always_true, target]);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_freeze_const_fold() {
let mut dag = make_dag();
let c = make_i32_const(&mut dag, 42);
let ty = Type::i32();
let _freeze = dag.add_node(SDOpcode::Freeze, vec![ty], vec![c]);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
}
#[derive(Debug, Clone)]
pub struct X86AddressingModeOptimizer {
pub num_optimized: usize,
max_disp: i64,
max_scale: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct X86AddressingMode {
pub base_reg: Option<u32>,
pub index_reg: Option<u32>,
pub scale: u8,
pub displacement: i64,
pub segment: Option<u32>,
}
impl Default for X86AddressingMode {
fn default() -> Self {
Self {
base_reg: None,
index_reg: None,
scale: 1,
displacement: 0,
segment: None,
}
}
}
impl X86AddressingModeOptimizer {
pub fn new() -> Self {
Self {
num_optimized: 0,
max_disp: i32::MAX as i64,
max_scale: 8,
}
}
pub fn try_fold_addressing(
&mut self,
base: Option<u32>,
index: Option<u32>,
scale: u8,
disp: i64,
) -> Option<X86AddressingMode> {
if scale != 1 && scale != 2 && scale != 4 && scale != 8 {
return None;
}
if disp < -self.max_disp || disp > self.max_disp {
return None;
}
if let (Some(b), Some(i)) = (base, index) {
if b == i && b != 0 {
return None;
}
}
self.num_optimized += 1;
Some(X86AddressingMode {
base_reg: base,
index_reg: index,
scale,
displacement: disp,
segment: None,
})
}
pub fn decompose_lea(
&mut self,
_pattern_type: u8,
base: u32,
index: u32,
scale: u8,
disp: i64,
) -> X86AddressingMode {
X86AddressingMode {
base_reg: Some(base),
index_reg: if index == base && scale == 0 {
None
} else {
Some(index)
},
scale: if scale == 0 { 1 } else { scale },
displacement: disp,
segment: None,
}
}
pub fn is_disp32(disp: i64) -> bool {
disp >= i32::MIN as i64 && disp <= i32::MAX as i64
}
pub fn is_disp8(disp: i64) -> bool {
disp >= -128 && disp <= 127
}
pub fn get_disp_size(disp: i64) -> u8 {
if disp == 0 {
0
} else if Self::is_disp8(disp) {
1
} else {
4
}
}
pub fn encode_modrm(mod_val: u8, reg: u8, rm: u8) -> u8 {
((mod_val & 0x03) << 6) | ((reg & 0x07) << 3) | (rm & 0x07)
}
pub fn encode_sib(scale: u8, index: u8, base: u8) -> u8 {
let scale_bits = match scale {
1 => 0,
2 => 1,
4 => 2,
8 => 3,
_ => 0,
};
((scale_bits & 0x03) << 6) | ((index & 0x07) << 3) | (base & 0x07)
}
pub fn is_stack_pointer(reg: u32) -> bool {
reg == 4 }
pub fn is_base_pointer(reg: u32) -> bool {
reg == 5 }
pub fn can_use_rip_relative(disp: i64) -> bool {
Self::is_disp32(disp)
}
}
#[derive(Debug, Clone)]
pub struct X86FrameLoweringPrep {
pub frame_size: u64,
pub stack_alignment: u32,
pub has_red_zone: bool,
pub red_zone_size: u64,
pub callee_saved: Vec<u32>,
pub can_omit_frame_pointer: bool,
pub local_area_size: u64,
pub max_call_frame_size: u64,
pub has_dynamic_alloca: bool,
}
impl Default for X86FrameLoweringPrep {
fn default() -> Self {
Self {
frame_size: 0,
stack_alignment: 16,
has_red_zone: false,
red_zone_size: 128,
callee_saved: Vec::new(),
can_omit_frame_pointer: true,
local_area_size: 0,
max_call_frame_size: 0,
has_dynamic_alloca: false,
}
}
}
impl X86FrameLoweringPrep {
pub fn new(is_64bit: bool, abi: X86ABIType) -> Self {
Self {
has_red_zone: is_64bit && matches!(abi, X86ABIType::SystemV),
..Default::default()
}
}
pub fn compute_callee_saved(&mut self, is_64bit: bool, abi: X86ABIType) {
self.callee_saved.clear();
match (is_64bit, abi) {
(true, X86ABIType::SystemV) => {
self.callee_saved.extend_from_slice(&[3, 5, 12, 13, 14, 15]);
}
(true, X86ABIType::Microsoft) => {
self.callee_saved
.extend_from_slice(&[3, 5, 6, 7, 12, 13, 14, 15]);
}
(false, _) => {
self.callee_saved.extend_from_slice(&[3, 5, 6, 7]);
}
_ => {
self.callee_saved.extend_from_slice(&[3, 5, 12, 13, 14, 15]);
}
}
}
pub fn check_frame_pointer_omission(&mut self, has_calls: bool, has_variable_alloca: bool) {
self.has_dynamic_alloca = has_variable_alloca;
self.can_omit_frame_pointer = !has_variable_alloca && self.frame_size <= 256;
if has_calls && self.frame_size > 0 {
self.can_omit_frame_pointer = false;
}
}
pub fn align_frame_size(&mut self) {
let align = self.stack_alignment as u64;
self.frame_size = (self.frame_size + align - 1) & !(align - 1);
}
pub fn can_use_red_zone(&self, offset: i64) -> bool {
self.has_red_zone && offset >= -(self.red_zone_size as i64) && offset < 0
}
pub fn num_callee_saved(&self) -> usize {
self.callee_saved.len()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ABIType {
SystemV,
Microsoft,
Cdecl,
Stdcall,
Fastcall,
Thiscall,
Vectorcall,
Regcall,
}
#[derive(Debug, Clone)]
pub struct X86ShrinkWrapPass {
pub num_shrink_wrapped: usize,
pub reg_first_use: HashMap<u32, usize>,
pub reg_last_use: HashMap<u32, usize>,
pub dominators: HashMap<usize, HashSet<usize>>,
pub post_dominators: HashMap<usize, HashSet<usize>>,
}
impl X86ShrinkWrapPass {
pub fn new() -> Self {
Self {
num_shrink_wrapped: 0,
reg_first_use: HashMap::new(),
reg_last_use: HashMap::new(),
dominators: HashMap::new(),
post_dominators: HashMap::new(),
}
}
pub fn analyze(&mut self, _mf: &MachineFunction, callee_saved: &[u32]) {
for ® in callee_saved {
self.reg_first_use.insert(reg, 0);
self.reg_last_use.insert(reg, 0);
}
}
pub fn find_save_point(&self, reg: u32) -> Option<usize> {
self.reg_first_use.get(®).copied()
}
pub fn find_restore_point(&self, reg: u32) -> Option<usize> {
self.reg_last_use.get(®).copied()
}
pub fn is_dominated_by(&self, block: usize, dominator: usize) -> bool {
self.dominators
.get(&block)
.map(|doms| doms.contains(&dominator))
.unwrap_or(false)
}
pub fn compute_dominator_sets(
&mut self,
blocks: &[usize],
edges: &[(usize, usize)],
entry: usize,
) {
let all_blocks: HashSet<usize> = blocks.iter().copied().collect();
for &b in blocks {
if b == entry {
let mut doms = HashSet::new();
doms.insert(entry);
self.dominators.insert(b, doms);
} else {
self.dominators.insert(b, all_blocks.clone());
}
}
let mut changed = true;
while changed {
changed = false;
for &b in blocks {
if b == entry {
continue;
}
let preds: Vec<usize> = edges
.iter()
.filter(|(_, to)| *to == b)
.map(|(from, _)| *from)
.collect();
let mut new_doms: HashSet<usize> = if preds.is_empty() {
all_blocks.clone()
} else {
let first = self.dominators.get(&preds[0]).cloned().unwrap_or_default();
preds.iter().skip(1).fold(first, |acc, p| {
let p_doms = self.dominators.get(p).cloned().unwrap_or_default();
acc.intersection(&p_doms).copied().collect()
})
};
new_doms.insert(b);
if new_doms != *self.dominators.get(&b).unwrap_or(&HashSet::new()) {
self.dominators.insert(b, new_doms);
changed = true;
}
}
}
}
pub fn is_profitable(&self) -> bool {
self.num_shrink_wrapped > 0
}
}
#[derive(Debug, Clone)]
pub struct X86MicroFusion {
pub num_fused: usize,
pub enabled: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FusionPair {
CmpJcc,
TestJcc,
AddJcc,
SubJcc,
AndJcc,
OrJcc,
DecJcc,
IncJcc,
}
impl X86MicroFusion {
pub fn new(enabled: bool) -> Self {
Self {
num_fused: 0,
enabled,
}
}
pub fn can_fuse(first: &X86MicroFusionOp, second: &X86MicroFusionOp) -> Option<FusionPair> {
match (first, second) {
(X86MicroFusionOp::Cmp, X86MicroFusionOp::Jcc) => Some(FusionPair::CmpJcc),
(X86MicroFusionOp::Test, X86MicroFusionOp::Jcc) => Some(FusionPair::TestJcc),
(X86MicroFusionOp::Add, X86MicroFusionOp::Jcc) => Some(FusionPair::AddJcc),
(X86MicroFusionOp::Sub, X86MicroFusionOp::Jcc) => Some(FusionPair::SubJcc),
(X86MicroFusionOp::And, X86MicroFusionOp::Jcc) => Some(FusionPair::AndJcc),
(X86MicroFusionOp::Or, X86MicroFusionOp::Jcc) => Some(FusionPair::OrJcc),
(X86MicroFusionOp::Dec, X86MicroFusionOp::Jcc) => Some(FusionPair::DecJcc),
(X86MicroFusionOp::Inc, X86MicroFusionOp::Jcc) => Some(FusionPair::IncJcc),
_ => None,
}
}
pub fn try_fuse(&mut self, first: &X86MicroFusionOp, second: &X86MicroFusionOp) -> bool {
if !self.enabled {
return false;
}
if Self::can_fuse(first, second).is_some() {
self.num_fused += 1;
true
} else {
false
}
}
pub fn can_eliminate_branch(&self, _pair: &FusionPair) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MicroFusionOp {
Cmp,
Test,
Add,
Sub,
And,
Or,
Dec,
Inc,
Jcc,
}
#[derive(Debug, Clone)]
pub struct X86MacroFusion {
pub num_macro_fused: usize,
pub supported_pairs: Vec<FusionPair>,
}
impl X86MacroFusion {
pub fn new(microarch: X86MicroArch) -> Self {
let supported = match microarch {
X86MicroArch::SandyBridge
| X86MicroArch::IvyBridge
| X86MicroArch::Haswell
| X86MicroArch::Broadwell => {
vec![FusionPair::CmpJcc, FusionPair::TestJcc]
}
X86MicroArch::Skylake
| X86MicroArch::KabyLake
| X86MicroArch::CoffeeLake
| X86MicroArch::CometLake => {
vec![
FusionPair::CmpJcc,
FusionPair::TestJcc,
FusionPair::AddJcc,
FusionPair::SubJcc,
FusionPair::AndJcc,
]
}
X86MicroArch::IceLake
| X86MicroArch::TigerLake
| X86MicroArch::AlderLake
| X86MicroArch::RaptorLake => {
vec![
FusionPair::CmpJcc,
FusionPair::TestJcc,
FusionPair::AddJcc,
FusionPair::SubJcc,
FusionPair::AndJcc,
FusionPair::OrJcc,
FusionPair::DecJcc,
FusionPair::IncJcc,
]
}
X86MicroArch::Zen1 | X86MicroArch::Zen2 => {
vec![FusionPair::CmpJcc, FusionPair::TestJcc]
}
X86MicroArch::Zen3 | X86MicroArch::Zen4 | X86MicroArch::Zen5 => {
vec![
FusionPair::CmpJcc,
FusionPair::TestJcc,
FusionPair::AddJcc,
FusionPair::SubJcc,
]
}
X86MicroArch::Generic => {
vec![FusionPair::CmpJcc, FusionPair::TestJcc]
}
};
Self {
num_macro_fused: 0,
supported_pairs: supported,
}
}
pub fn supports(&self, pair: &FusionPair) -> bool {
self.supported_pairs.contains(pair)
}
pub fn try_macro_fuse(&mut self, first: &X86MicroFusionOp, second: &X86MicroFusionOp) -> bool {
if let Some(pair) = X86MicroFusion::can_fuse(first, second) {
if self.supports(&pair) {
self.num_macro_fused += 1;
return true;
}
}
false
}
pub fn fusion_count(&self) -> usize {
self.num_macro_fused
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MicroArch {
Generic,
SandyBridge,
IvyBridge,
Haswell,
Broadwell,
Skylake,
KabyLake,
CoffeeLake,
CometLake,
IceLake,
TigerLake,
AlderLake,
RaptorLake,
Zen1,
Zen2,
Zen3,
Zen4,
Zen5,
}
#[derive(Debug, Clone)]
pub struct X86VPack {
pub num_optimized: usize,
pub min_alignment: u32,
}
impl X86VPack {
pub fn new() -> Self {
Self {
num_optimized: 0,
min_alignment: 1,
}
}
pub fn can_pack_loads(load1_addr: u64, load2_addr: u64, size: u64, alignment: u64) -> bool {
load1_addr + size == load2_addr
&& load1_addr % alignment == 0
&& (load2_addr - load1_addr) % alignment == 0
}
pub fn can_pack_stores(store1_addr: u64, store2_addr: u64, size: u64, alignment: u64) -> bool {
Self::can_pack_loads(store1_addr, store2_addr, size, alignment)
}
pub fn try_pack(&mut self, addrs: &[u64], sizes: &[u64], alignment: u64) -> Vec<usize> {
let mut packed_idxs = Vec::new();
let mut i = 0;
while i + 1 < addrs.len() {
if Self::can_pack_loads(addrs[i], addrs[i + 1], sizes[i], alignment) {
packed_idxs.push(i);
self.num_optimized += 1;
i += 2;
} else {
i += 1;
}
}
packed_idxs
}
pub fn can_simplify_interleave(_vec_len: u32, _elem_size: u32) -> bool {
true
}
pub fn can_simplify_deinterleave(_vec_len: u32, _elem_size: u32) -> bool {
true
}
}
#[derive(Debug, Clone)]
pub struct X86ExecutionDomainFix {
pub num_fixed: usize,
pub total_bypass_saved: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86ExecDomain {
Generic,
Integer,
SsePacked,
SseScalar,
AvxPacked,
AvxScalar,
}
impl X86ExecutionDomainFix {
pub fn new() -> Self {
Self {
num_fixed: 0,
total_bypass_saved: 0,
}
}
pub fn get_exec_domain(opcode: X86Opcode) -> X86ExecDomain {
match opcode {
X86Opcode::ADD
| X86Opcode::SUB
| X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
| X86Opcode::CMP
| X86Opcode::MOV => X86ExecDomain::Integer,
X86Opcode::ADDSS | X86Opcode::SUBSS | X86Opcode::MULSS | X86Opcode::DIVSS => {
X86ExecDomain::SseScalar
}
X86Opcode::ADDSD | X86Opcode::SUBSD | X86Opcode::MULSD | X86Opcode::DIVSD => {
X86ExecDomain::SseScalar
}
X86Opcode::ANDPS | X86Opcode::ORPS | X86Opcode::XORPS => X86ExecDomain::SsePacked,
_ => X86ExecDomain::Generic,
}
}
pub fn get_bypass_delay(from: X86ExecDomain, to: X86ExecDomain) -> u64 {
if from == to {
return 0;
}
match (from, to) {
(X86ExecDomain::Integer, X86ExecDomain::SsePacked) => 2,
(X86ExecDomain::SsePacked, X86ExecDomain::Integer) => 2,
(X86ExecDomain::SseScalar, X86ExecDomain::SsePacked) => 1,
(X86ExecDomain::SsePacked, X86ExecDomain::SseScalar) => 1,
_ => 1,
}
}
pub fn try_fix_domain(
&mut self,
_current_domain: X86ExecDomain,
_preferred_domain: X86ExecDomain,
) -> bool {
self.num_fixed += 1;
true
}
pub fn has_alternative_domain(opcode: X86Opcode, _target_domain: X86ExecDomain) -> bool {
matches!(
opcode,
X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
| X86Opcode::ANDPS
| X86Opcode::ORPS
| X86Opcode::XORPS
)
}
}
#[derive(Debug, Clone)]
pub struct X86FixupBW {
pub num_fixed: usize,
pub num_promoted: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86RegClass {
GR8, GR16, GR32, GR64, XMM, YMM, ZMM, }
impl X86FixupBW {
pub fn new() -> Self {
Self {
num_fixed: 0,
num_promoted: 0,
}
}
pub fn may_cause_partial_stall(dst_class: X86RegClass, _src_class: X86RegClass) -> bool {
matches!(dst_class, X86RegClass::GR8 | X86RegClass::GR16)
}
pub fn fix_partial_stall(&mut self, _dst_reg: u32, _prefer_promote: bool) -> X86FixupAction {
self.num_fixed += 1;
if _prefer_promote {
X86FixupAction::PromoteTo32
} else {
X86FixupAction::InsertMovzx
}
}
pub fn promote_to_32bit(&mut self) {
self.num_promoted += 1;
}
pub fn needs_fixup(_opcode: X86Opcode, _reg_class: X86RegClass) -> bool {
matches!(_reg_class, X86RegClass::GR8 | X86RegClass::GR16)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86FixupAction {
InsertMovzx,
PromoteTo32,
InsertXorBreak,
NoAction,
}
#[derive(Debug, Clone)]
pub struct X86ClflushOpt {
pub num_optimized: usize,
pub num_redundant_removed: usize,
pub has_clflushopt: bool,
pub has_clwb: bool,
}
impl X86ClflushOpt {
pub fn new(has_clflushopt: bool, has_clwb: bool) -> Self {
Self {
num_optimized: 0,
num_redundant_removed: 0,
has_clflushopt,
has_clwb,
}
}
pub fn same_cache_line(addr1: u64, addr2: u64) -> bool {
(addr1 ^ addr2) < 64
}
pub fn remove_redundant(&mut self, addresses: &[u64]) -> Vec<u64> {
let mut unique = Vec::new();
let mut seen: HashSet<u64> = HashSet::new();
for &addr in addresses {
let line = addr & !63; if seen.insert(line) {
unique.push(addr);
} else {
self.num_redundant_removed += 1;
}
}
self.num_optimized = unique.len();
unique
}
pub fn can_batch(&self, _addrs: &[u64]) -> bool {
self.has_clflushopt
}
pub fn prefer_clwb(&self) -> bool {
self.has_clwb
}
}
#[derive(Debug, Clone)]
pub struct X86PadShortFunctions {
pub num_padded: usize,
pub alignment: u32,
pub max_function_size: u32,
}
impl X86PadShortFunctions {
pub fn new(alignment: u32) -> Self {
Self {
num_padded: 0,
alignment,
max_function_size: 64,
}
}
pub fn should_pad(&self, function_size: u32) -> bool {
function_size <= self.max_function_size
}
pub fn compute_padding(&self, current_offset: u32) -> u32 {
let remainder = current_offset % self.alignment;
if remainder == 0 {
0
} else {
self.alignment - remainder
}
}
pub fn generate_nops(count: u32) -> Vec<u8> {
match count {
0 => vec![],
1 => vec![0x90],
2 => vec![0x66, 0x90],
3 => vec![0x0F, 0x1F, 0x00],
4 => vec![0x0F, 0x1F, 0x40, 0x00],
5 => vec![0x0F, 0x1F, 0x44, 0x00, 0x00],
6 => vec![0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00],
7 => vec![0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00],
8 => vec![0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00],
9 => {
let mut nops = vec![0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00];
nops.resize(count as usize, 0x90);
nops
}
_ => {
let mut result = Vec::new();
let mut remaining = count;
while remaining >= 9 {
result
.extend_from_slice(&[0x66, 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00]);
remaining -= 9;
}
result.extend(Self::generate_nops(remaining));
result
}
}
}
pub fn pad_function(&mut self, _func_offset: u32) -> Vec<u8> {
self.num_padded += 1;
Self::generate_nops(self.compute_padding(_func_offset))
}
}
#[derive(Debug, Clone)]
pub struct X86UnrollPrefetch {
pub num_prefetches: usize,
pub prefetch_distance: u32,
pub cache_line_size: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86PrefetchHint {
T0, T1, T2, NTA, }
impl X86UnrollPrefetch {
pub fn new() -> Self {
Self {
num_prefetches: 0,
prefetch_distance: 8,
cache_line_size: 64,
}
}
pub fn compute_prefetch_distance(&self, latency: u32, bytes_per_iter: u32) -> u32 {
(latency.saturating_mul(bytes_per_iter) + self.cache_line_size - 1) / self.cache_line_size
}
pub fn should_prefetch(&self, trip_count: u32, stride: u32) -> bool {
trip_count >= self.prefetch_distance && stride > 0 && stride <= self.cache_line_size * 4
}
pub fn insert_prefetch(&mut self, _addr: u64, _hint: X86PrefetchHint) {
self.num_prefetches += 1;
}
pub fn select_hint(reuse_distance: u32) -> X86PrefetchHint {
if reuse_distance <= 2 {
X86PrefetchHint::T0
} else if reuse_distance <= 8 {
X86PrefetchHint::T1
} else if reuse_distance <= 64 {
X86PrefetchHint::T2
} else {
X86PrefetchHint::NTA
}
}
}
#[derive(Debug, Clone)]
pub struct X86BranchRelaxation {
pub num_relaxed: usize,
pub max_short_offset: i32,
}
impl X86BranchRelaxation {
pub fn new() -> Self {
Self {
num_relaxed: 0,
max_short_offset: 127,
}
}
pub fn is_short_branch_range(offset: i64) -> bool {
offset >= -128 && offset <= 127
}
pub fn branch_size(is_short: bool) -> u32 {
if is_short {
2
} else {
6
}
}
pub fn relax_if_needed(&mut self, offset: i64) -> u32 {
if Self::is_short_branch_range(offset) {
2
} else {
self.num_relaxed += 1;
6
}
}
pub fn jmp_size(offset: i64) -> u32 {
if offset >= -128 && offset <= 127 {
2 } else if offset >= i32::MIN as i64 && offset <= i32::MAX as i64 {
5 } else {
0 }
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_addressing_mode_creation() {
let opt = X86AddressingModeOptimizer::new();
assert_eq!(opt.num_optimized, 0);
assert!(opt.max_disp > 0);
}
#[test]
fn test_fold_addressing_simple() {
let mut opt = X86AddressingModeOptimizer::new();
let result = opt.try_fold_addressing(Some(0), None, 1, 16);
assert!(result.is_some());
let am = result.unwrap();
assert_eq!(am.base_reg, Some(0));
assert_eq!(am.displacement, 16);
assert_eq!(am.scale, 1);
}
#[test]
fn test_fold_addressing_index_scale() {
let mut opt = X86AddressingModeOptimizer::new();
let result = opt.try_fold_addressing(Some(1), Some(2), 4, 0);
assert!(result.is_some());
let am = result.unwrap();
assert_eq!(am.base_reg, Some(1));
assert_eq!(am.index_reg, Some(2));
assert_eq!(am.scale, 4);
}
#[test]
fn test_fold_addressing_invalid_scale() {
let mut opt = X86AddressingModeOptimizer::new();
let result = opt.try_fold_addressing(Some(1), Some(2), 3, 0);
assert!(result.is_none());
}
#[test]
fn test_fold_addressing_invalid_disp() {
let mut opt = X86AddressingModeOptimizer::new();
opt.max_disp = 100;
let result = opt.try_fold_addressing(Some(1), None, 1, 200);
assert!(result.is_none());
}
#[test]
fn test_fold_addressing_same_base_index() {
let mut opt = X86AddressingModeOptimizer::new();
let result = opt.try_fold_addressing(Some(3), Some(3), 1, 0);
assert!(result.is_none());
}
#[test]
fn test_is_disp8() {
assert!(X86AddressingModeOptimizer::is_disp8(0));
assert!(X86AddressingModeOptimizer::is_disp8(127));
assert!(X86AddressingModeOptimizer::is_disp8(-128));
assert!(!X86AddressingModeOptimizer::is_disp8(128));
assert!(!X86AddressingModeOptimizer::is_disp8(-129));
}
#[test]
fn test_is_disp32() {
assert!(X86AddressingModeOptimizer::is_disp32(0));
assert!(X86AddressingModeOptimizer::is_disp32(i32::MAX as i64));
assert!(X86AddressingModeOptimizer::is_disp32(i32::MIN as i64));
assert!(!X86AddressingModeOptimizer::is_disp32(i32::MAX as i64 + 1));
}
#[test]
fn test_get_disp_size() {
assert_eq!(X86AddressingModeOptimizer::get_disp_size(0), 0);
assert_eq!(X86AddressingModeOptimizer::get_disp_size(100), 1);
assert_eq!(X86AddressingModeOptimizer::get_disp_size(-100), 1);
assert_eq!(X86AddressingModeOptimizer::get_disp_size(1000), 4);
}
#[test]
fn test_encode_modrm() {
assert_eq!(X86AddressingModeOptimizer::encode_modrm(0, 0, 0), 0x00);
assert_eq!(X86AddressingModeOptimizer::encode_modrm(3, 0, 0), 0xC0);
assert_eq!(X86AddressingModeOptimizer::encode_modrm(0, 1, 0), 0x08);
assert_eq!(X86AddressingModeOptimizer::encode_modrm(0, 0, 1), 0x01);
}
#[test]
fn test_encode_sib() {
assert_eq!(X86AddressingModeOptimizer::encode_sib(1, 0, 0), 0x00);
assert_eq!(X86AddressingModeOptimizer::encode_sib(2, 0, 0), 0x40);
assert_eq!(X86AddressingModeOptimizer::encode_sib(4, 0, 0), 0x80);
assert_eq!(X86AddressingModeOptimizer::encode_sib(8, 0, 0), 0xC0);
}
#[test]
fn test_is_stack_pointer() {
assert!(X86AddressingModeOptimizer::is_stack_pointer(4));
assert!(!X86AddressingModeOptimizer::is_stack_pointer(0));
}
#[test]
fn test_is_base_pointer() {
assert!(X86AddressingModeOptimizer::is_base_pointer(5));
assert!(!X86AddressingModeOptimizer::is_base_pointer(0));
}
#[test]
fn test_addressing_mode_default() {
let am = X86AddressingMode::default();
assert_eq!(am.base_reg, None);
assert_eq!(am.index_reg, None);
assert_eq!(am.scale, 1);
assert_eq!(am.displacement, 0);
}
#[test]
fn test_frame_lowering_creation() {
let prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
assert!(prep.has_red_zone);
assert_eq!(prep.red_zone_size, 128);
assert_eq!(prep.frame_size, 0);
}
#[test]
fn test_frame_lowering_no_red_zone_32bit() {
let prep = X86FrameLoweringPrep::new(false, X86ABIType::SystemV);
assert!(!prep.has_red_zone);
}
#[test]
fn test_frame_lowering_no_red_zone_win64() {
let prep = X86FrameLoweringPrep::new(true, X86ABIType::Microsoft);
assert!(!prep.has_red_zone);
}
#[test]
fn test_compute_callee_saved_sysv64() {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
prep.compute_callee_saved(true, X86ABIType::SystemV);
assert!(prep.callee_saved.contains(&3)); assert!(prep.callee_saved.contains(&5)); assert!(prep.callee_saved.contains(&12)); }
#[test]
fn test_compute_callee_saved_win64() {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::Microsoft);
prep.compute_callee_saved(true, X86ABIType::Microsoft);
assert!(prep.callee_saved.contains(&6)); assert!(prep.callee_saved.contains(&7)); }
#[test]
fn test_compute_callee_saved_32bit() {
let mut prep = X86FrameLoweringPrep::new(false, X86ABIType::Cdecl);
prep.compute_callee_saved(false, X86ABIType::Cdecl);
assert!(prep.callee_saved.contains(&3)); assert!(prep.callee_saved.contains(&5)); }
#[test]
fn test_check_frame_pointer_omission() {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
prep.frame_size = 64;
prep.check_frame_pointer_omission(false, false);
assert!(prep.can_omit_frame_pointer);
}
#[test]
fn test_check_frame_pointer_omission_with_calls() {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
prep.frame_size = 64;
prep.check_frame_pointer_omission(true, false);
assert!(!prep.can_omit_frame_pointer);
}
#[test]
fn test_check_frame_pointer_omission_variable_alloca() {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
prep.check_frame_pointer_omission(false, true);
assert!(!prep.can_omit_frame_pointer);
}
#[test]
fn test_align_frame_size() {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
prep.frame_size = 65;
prep.stack_alignment = 16;
prep.align_frame_size();
assert_eq!(prep.frame_size, 80); }
#[test]
fn test_align_frame_size_already_aligned() {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
prep.frame_size = 64;
prep.stack_alignment = 16;
prep.align_frame_size();
assert_eq!(prep.frame_size, 64);
}
#[test]
fn test_can_use_red_zone() {
let prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
assert!(prep.can_use_red_zone(-8));
assert!(prep.can_use_red_zone(-128));
assert!(!prep.can_use_red_zone(0));
assert!(!prep.can_use_red_zone(-129));
}
#[test]
fn test_num_callee_saved() {
let mut prep = X86FrameLoweringPrep::new(false, X86ABIType::Cdecl);
prep.compute_callee_saved(false, X86ABIType::Cdecl);
assert_eq!(prep.num_callee_saved(), 4);
}
#[test]
fn test_shrink_wrap_creation() {
let sw = X86ShrinkWrapPass::new();
assert_eq!(sw.num_shrink_wrapped, 0);
assert!(!sw.is_profitable());
}
#[test]
fn test_dominator_computation_simple() {
let mut sw = X86ShrinkWrapPass::new();
let blocks = vec![0, 1, 2];
let edges = vec![(0, 1), (0, 2)];
sw.compute_dominator_sets(&blocks, &edges, 0);
assert!(sw.is_dominated_by(1, 0));
assert!(sw.is_dominated_by(2, 0));
}
#[test]
fn test_dominator_computation_chain() {
let mut sw = X86ShrinkWrapPass::new();
let blocks = vec![0, 1, 2, 3];
let edges = vec![(0, 1), (1, 2), (2, 3)];
sw.compute_dominator_sets(&blocks, &edges, 0);
assert!(sw.is_dominated_by(3, 0));
assert!(sw.is_dominated_by(3, 1));
assert!(sw.is_dominated_by(3, 2));
assert!(!sw.is_dominated_by(1, 2));
}
#[test]
fn test_find_save_restore_points() {
let mut sw = X86ShrinkWrapPass::new();
sw.reg_first_use.insert(3, 1);
sw.reg_last_use.insert(3, 5);
assert_eq!(sw.find_save_point(3), Some(1));
assert_eq!(sw.find_restore_point(3), Some(5));
}
#[test]
fn test_micro_fusion_creation() {
let mf = X86MicroFusion::new(true);
assert!(mf.enabled);
assert_eq!(mf.num_fused, 0);
}
#[test]
fn test_can_fuse_cmp_jcc() {
let result = X86MicroFusion::can_fuse(&X86MicroFusionOp::Cmp, &X86MicroFusionOp::Jcc);
assert!(result.is_some());
assert_eq!(result.unwrap(), FusionPair::CmpJcc);
}
#[test]
fn test_can_fuse_test_jcc() {
let result = X86MicroFusion::can_fuse(&X86MicroFusionOp::Test, &X86MicroFusionOp::Jcc);
assert!(result.is_some());
}
#[test]
fn test_cannot_fuse_invalid_pair() {
let result = X86MicroFusion::can_fuse(&X86MicroFusionOp::Cmp, &X86MicroFusionOp::Add);
assert!(result.is_none());
}
#[test]
fn test_micro_fusion_disabled() {
let mut mf = X86MicroFusion::new(false);
let result = mf.try_fuse(&X86MicroFusionOp::Cmp, &X86MicroFusionOp::Jcc);
assert!(!result);
assert_eq!(mf.num_fused, 0);
}
#[test]
fn test_macro_fusion_skylake_supports() {
let mf = X86MacroFusion::new(X86MicroArch::Skylake);
assert!(mf.supports(&FusionPair::CmpJcc));
assert!(mf.supports(&FusionPair::TestJcc));
assert!(mf.supports(&FusionPair::AddJcc));
assert!(mf.supports(&FusionPair::SubJcc));
}
#[test]
fn test_macro_fusion_sandybridge_limited() {
let mf = X86MacroFusion::new(X86MicroArch::SandyBridge);
assert!(mf.supports(&FusionPair::CmpJcc));
assert!(!mf.supports(&FusionPair::AddJcc));
}
#[test]
fn test_macro_fusion_icelake_full() {
let mf = X86MacroFusion::new(X86MicroArch::IceLake);
assert!(mf.supports(&FusionPair::DecJcc));
assert!(mf.supports(&FusionPair::IncJcc));
}
#[test]
fn test_macro_fuse_pair() {
let mut mf = X86MacroFusion::new(X86MicroArch::Skylake);
let result = mf.try_macro_fuse(&X86MicroFusionOp::Cmp, &X86MicroFusionOp::Jcc);
assert!(result);
assert_eq!(mf.fusion_count(), 1);
}
#[test]
fn test_macro_fuse_unsupported() {
let mut mf = X86MacroFusion::new(X86MicroArch::SandyBridge);
let result = mf.try_macro_fuse(&X86MicroFusionOp::Dec, &X86MicroFusionOp::Jcc);
assert!(!result);
assert_eq!(mf.fusion_count(), 0);
}
#[test]
fn test_vpack_creation() {
let vp = X86VPack::new();
assert_eq!(vp.num_optimized, 0);
assert_eq!(vp.min_alignment, 1);
}
#[test]
fn test_can_pack_loads_adjacent() {
assert!(X86VPack::can_pack_loads(0, 4, 4, 4));
assert!(X86VPack::can_pack_loads(64, 72, 8, 8));
}
#[test]
fn test_cannot_pack_loads_not_adjacent() {
assert!(!X86VPack::can_pack_loads(0, 8, 4, 4));
}
#[test]
fn test_cannot_pack_loads_misaligned() {
assert!(!X86VPack::can_pack_loads(1, 5, 4, 4));
}
#[test]
fn test_try_pack() {
let mut vp = X86VPack::new();
let addrs = vec![0, 4, 8, 12];
let sizes = vec![4, 4, 4, 4];
let packed = vp.try_pack(&addrs, &sizes, 4);
assert_eq!(packed.len(), 2); }
#[test]
fn test_exec_domain_fix_creation() {
let fix = X86ExecutionDomainFix::new();
assert_eq!(fix.num_fixed, 0);
assert_eq!(fix.total_bypass_saved, 0);
}
#[test]
fn test_get_exec_domain_integer() {
assert_eq!(
X86ExecutionDomainFix::get_exec_domain(X86Opcode::ADD),
X86ExecDomain::Integer
);
assert_eq!(
X86ExecutionDomainFix::get_exec_domain(X86Opcode::MOV),
X86ExecDomain::Integer
);
}
#[test]
fn test_get_exec_domain_sse() {
assert_eq!(
X86ExecutionDomainFix::get_exec_domain(X86Opcode::ADDSS),
X86ExecDomain::SseScalar
);
assert_eq!(
X86ExecutionDomainFix::get_exec_domain(X86Opcode::ANDPS),
X86ExecDomain::SsePacked
);
}
#[test]
fn test_bypass_delay_same_domain() {
assert_eq!(
X86ExecutionDomainFix::get_bypass_delay(X86ExecDomain::Integer, X86ExecDomain::Integer),
0
);
}
#[test]
fn test_bypass_delay_cross_domain() {
assert_eq!(
X86ExecutionDomainFix::get_bypass_delay(
X86ExecDomain::Integer,
X86ExecDomain::SsePacked
),
2
);
assert_eq!(
X86ExecutionDomainFix::get_bypass_delay(
X86ExecDomain::SseScalar,
X86ExecDomain::SsePacked
),
1
);
}
#[test]
fn test_has_alternative_domain() {
assert!(X86ExecutionDomainFix::has_alternative_domain(
X86Opcode::AND,
X86ExecDomain::SsePacked
));
assert!(!X86ExecutionDomainFix::has_alternative_domain(
X86Opcode::ADD,
X86ExecDomain::SsePacked
));
}
#[test]
fn test_fixup_bw_creation() {
let fbw = X86FixupBW::new();
assert_eq!(fbw.num_fixed, 0);
assert_eq!(fbw.num_promoted, 0);
}
#[test]
fn test_may_cause_partial_stall() {
assert!(X86FixupBW::may_cause_partial_stall(
X86RegClass::GR8,
X86RegClass::GR32
));
assert!(X86FixupBW::may_cause_partial_stall(
X86RegClass::GR16,
X86RegClass::GR32
));
assert!(!X86FixupBW::may_cause_partial_stall(
X86RegClass::GR32,
X86RegClass::GR32
));
}
#[test]
fn test_needs_fixup() {
assert!(X86FixupBW::needs_fixup(X86Opcode::ADD, X86RegClass::GR8));
assert!(!X86FixupBW::needs_fixup(X86Opcode::ADD, X86RegClass::GR32));
}
#[test]
fn test_fix_partial_stall_promote() {
let mut fbw = X86FixupBW::new();
let action = fbw.fix_partial_stall(0, true);
assert_eq!(action, X86FixupAction::PromoteTo32);
assert_eq!(fbw.num_fixed, 1);
}
#[test]
fn test_fix_partial_stall_movzx() {
let mut fbw = X86FixupBW::new();
let action = fbw.fix_partial_stall(0, false);
assert_eq!(action, X86FixupAction::InsertMovzx);
}
#[test]
fn test_clflush_opt_creation() {
let co = X86ClflushOpt::new(true, false);
assert!(co.has_clflushopt);
assert!(!co.has_clwb);
}
#[test]
fn test_same_cache_line() {
assert!(X86ClflushOpt::same_cache_line(0, 63));
assert!(X86ClflushOpt::same_cache_line(64, 127));
assert!(!X86ClflushOpt::same_cache_line(0, 64));
}
#[test]
fn test_remove_redundant_flushes() {
let mut co = X86ClflushOpt::new(false, false);
let addrs = vec![0, 10, 64, 70, 0]; let unique = co.remove_redundant(&addrs);
assert_eq!(unique.len(), 2); assert_eq!(co.num_redundant_removed, 2); }
#[test]
fn test_prefer_clwb() {
let co = X86ClflushOpt::new(true, true);
assert!(co.prefer_clwb());
}
#[test]
fn test_pad_short_functions_creation() {
let pad = X86PadShortFunctions::new(16);
assert_eq!(pad.alignment, 16);
assert_eq!(pad.num_padded, 0);
}
#[test]
fn test_should_pad() {
let pad = X86PadShortFunctions::new(16);
assert!(pad.should_pad(32));
assert!(pad.should_pad(64));
assert!(!pad.should_pad(128));
}
#[test]
fn test_compute_padding() {
let pad = X86PadShortFunctions::new(16);
assert_eq!(pad.compute_padding(0), 0);
assert_eq!(pad.compute_padding(1), 15);
assert_eq!(pad.compute_padding(16), 0);
assert_eq!(pad.compute_padding(17), 15);
}
#[test]
fn test_generate_nops() {
assert_eq!(X86PadShortFunctions::generate_nops(0), vec![]);
assert_eq!(X86PadShortFunctions::generate_nops(1), vec![0x90]);
assert_eq!(X86PadShortFunctions::generate_nops(2), vec![0x66, 0x90]);
assert_eq!(
X86PadShortFunctions::generate_nops(3),
vec![0x0F, 0x1F, 0x00]
);
}
#[test]
fn test_generate_nops_large() {
let nops = X86PadShortFunctions::generate_nops(15);
assert_eq!(nops.len(), 15);
}
#[test]
fn test_pad_function() {
let mut pad = X86PadShortFunctions::new(16);
let nops = pad.pad_function(3);
assert!(!nops.is_empty());
assert_eq!(pad.num_padded, 1);
}
#[test]
fn test_unroll_prefetch_creation() {
let up = X86UnrollPrefetch::new();
assert_eq!(up.prefetch_distance, 8);
assert_eq!(up.cache_line_size, 64);
}
#[test]
fn test_compute_prefetch_distance() {
let up = X86UnrollPrefetch::new();
let distance = up.compute_prefetch_distance(20, 4);
assert_eq!(distance, 2);
}
#[test]
fn test_should_prefetch() {
let up = X86UnrollPrefetch::new();
assert!(up.should_prefetch(16, 4));
assert!(!up.should_prefetch(4, 4));
assert!(!up.should_prefetch(16, 0));
}
#[test]
fn test_select_hint() {
assert_eq!(X86UnrollPrefetch::select_hint(1), X86PrefetchHint::T0);
assert_eq!(X86UnrollPrefetch::select_hint(5), X86PrefetchHint::T1);
assert_eq!(X86UnrollPrefetch::select_hint(32), X86PrefetchHint::T2);
assert_eq!(X86UnrollPrefetch::select_hint(128), X86PrefetchHint::NTA);
}
#[test]
fn test_insert_prefetch() {
let mut up = X86UnrollPrefetch::new();
up.insert_prefetch(0x1000, X86PrefetchHint::T0);
up.insert_prefetch(0x2000, X86PrefetchHint::NTA);
assert_eq!(up.num_prefetches, 2);
}
#[test]
fn test_branch_relaxation_creation() {
let br = X86BranchRelaxation::new();
assert_eq!(br.num_relaxed, 0);
assert_eq!(br.max_short_offset, 127);
}
#[test]
fn test_is_short_branch_range() {
assert!(X86BranchRelaxation::is_short_branch_range(0));
assert!(X86BranchRelaxation::is_short_branch_range(127));
assert!(X86BranchRelaxation::is_short_branch_range(-128));
assert!(!X86BranchRelaxation::is_short_branch_range(128));
assert!(!X86BranchRelaxation::is_short_branch_range(-129));
}
#[test]
fn test_branch_size() {
assert_eq!(X86BranchRelaxation::branch_size(true), 2);
assert_eq!(X86BranchRelaxation::branch_size(false), 6);
}
#[test]
fn test_relax_if_needed_short() {
let mut br = X86BranchRelaxation::new();
let size = br.relax_if_needed(50);
assert_eq!(size, 2);
assert_eq!(br.num_relaxed, 0);
}
#[test]
fn test_relax_if_needed_near() {
let mut br = X86BranchRelaxation::new();
let size = br.relax_if_needed(200);
assert_eq!(size, 6);
assert_eq!(br.num_relaxed, 1);
}
#[test]
fn test_jmp_size() {
assert_eq!(X86BranchRelaxation::jmp_size(0), 2);
assert_eq!(X86BranchRelaxation::jmp_size(200), 5);
assert_eq!(X86BranchRelaxation::jmp_size(i32::MAX as i64 + 1), 0);
}
}
#[derive(Debug, Clone)]
pub struct X86LoadStoreOptimizer {
pub num_combined_loads: usize,
pub num_combined_stores: usize,
pub min_alignment: u32,
}
impl X86LoadStoreOptimizer {
pub fn new() -> Self {
Self {
num_combined_loads: 0,
num_combined_stores: 0,
min_alignment: 1,
}
}
pub fn can_combine_loads(addr1: u64, addr2: u64, size1: u64, size2: u64) -> bool {
addr1 + size1 == addr2
&& size1 == size2
&& (size1 == 1 || size1 == 2 || size1 == 4 || size1 == 8 || size1 == 16 || size1 == 32)
}
pub fn can_combine_stores(addr1: u64, addr2: u64, size1: u64, size2: u64) -> bool {
Self::can_combine_loads(addr1, addr2, size1, size2)
}
pub fn combined_size(size: u64) -> u64 {
size * 2
}
pub fn try_combine_loads(&mut self, loads: &[(u64, u64)]) -> Vec<(u64, u64)> {
let mut result = Vec::new();
let mut i = 0;
while i < loads.len() {
if i + 1 < loads.len() {
let (a1, s1) = loads[i];
let (a2, s2) = loads[i + 1];
if Self::can_combine_loads(a1, a2, s1, s2) {
result.push((a1, Self::combined_size(s1)));
self.num_combined_loads += 1;
i += 2;
continue;
}
}
result.push(loads[i]);
i += 1;
}
result
}
pub fn try_combine_stores(&mut self, stores: &[(u64, u64)]) -> Vec<(u64, u64)> {
let mut result = Vec::new();
let mut i = 0;
while i < stores.len() {
if i + 1 < stores.len() {
let (a1, s1) = stores[i];
let (a2, s2) = stores[i + 1];
if Self::can_combine_stores(a1, a2, s1, s2) {
result.push((a1, Self::combined_size(s1)));
self.num_combined_stores += 1;
i += 2;
continue;
}
}
result.push(stores[i]);
i += 1;
}
result
}
pub fn is_always_beneficial(combined_size: u64) -> bool {
combined_size <= 32
}
}
#[derive(Debug, Clone)]
pub struct X86LZCNTBSFOpt {
pub has_lzcnt: bool,
pub has_tzcnt: bool,
pub has_bmi1: bool,
pub num_optimized: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CLZImpl {
LZCNT,
BSR,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86CTZImpl {
TZCNT,
BSF,
BLSI,
}
impl X86LZCNTBSFOpt {
pub fn new(features: &X86TargetFeatures) -> Self {
Self {
has_lzcnt: features.has_lzcnt,
has_tzcnt: features.has_lzcnt,
has_bmi1: features.has_bmi,
num_optimized: 0,
}
}
pub fn select_clz_impl(&self) -> X86CLZImpl {
if self.has_lzcnt {
X86CLZImpl::LZCNT
} else {
X86CLZImpl::BSR
}
}
pub fn select_ctz_impl(&self) -> X86CTZImpl {
if self.has_tzcnt {
X86CTZImpl::TZCNT
} else if self.has_bmi1 {
X86CTZImpl::BLSI
} else {
X86CTZImpl::BSF
}
}
pub fn needs_zero_fixup(&self, impl_type: X86CLZImpl) -> bool {
impl_type == X86CLZImpl::BSR
}
pub fn generate_zero_fixup() -> Vec<X86Opcode> {
vec![X86Opcode::CMOVE, X86Opcode::MOV]
}
pub fn can_encode_lzcnt(&self) -> bool {
self.has_lzcnt
}
pub fn can_encode_tzcnt(&self) -> bool {
self.has_tzcnt
}
pub fn can_use_blsi_for_ctz(&self) -> bool {
self.has_bmi1
}
}
#[derive(Debug, Clone)]
pub struct X86SchedModelInfo {
pub microarch: X86MicroArch,
pub issue_width: u32,
pub num_ports: u32,
pub load_latency: u32,
pub store_latency: u32,
pub branch_mispredict_penalty: u32,
pub has_uop_cache: bool,
}
impl X86SchedModelInfo {
pub fn new(microarch: X86MicroArch) -> Self {
match microarch {
X86MicroArch::Generic => Self {
microarch,
issue_width: 4,
num_ports: 8,
load_latency: 5,
store_latency: 1,
branch_mispredict_penalty: 16,
has_uop_cache: true,
},
X86MicroArch::Skylake => Self {
microarch,
issue_width: 4,
num_ports: 8,
load_latency: 5,
store_latency: 1,
branch_mispredict_penalty: 16,
has_uop_cache: true,
},
X86MicroArch::IceLake => Self {
microarch,
issue_width: 5,
num_ports: 10,
load_latency: 5,
store_latency: 1,
branch_mispredict_penalty: 16,
has_uop_cache: true,
},
X86MicroArch::Zen3 => Self {
microarch,
issue_width: 6,
num_ports: 6,
load_latency: 4,
store_latency: 1,
branch_mispredict_penalty: 18,
has_uop_cache: true,
},
X86MicroArch::Zen4 => Self {
microarch,
issue_width: 6,
num_ports: 8,
load_latency: 4,
store_latency: 1,
branch_mispredict_penalty: 18,
has_uop_cache: true,
},
_ => Self {
microarch,
issue_width: 4,
num_ports: 8,
load_latency: 4,
store_latency: 1,
branch_mispredict_penalty: 16,
has_uop_cache: true,
},
}
}
pub fn alu_latency(&self) -> u32 {
1
}
pub fn imul_latency(&self) -> u32 {
3
}
pub fn idiv_latency(&self, bit_width: u32) -> u32 {
if bit_width == 64 {
42
} else {
26
}
}
pub fn alu_throughput(&self) -> f64 {
0.25
}
pub fn macro_fusion_benefit(&self) -> bool {
self.microarch as u32 >= X86MicroArch::SandyBridge as u32
}
pub fn taken_branch_cost(&self, predicted_correctly: bool) -> u32 {
if predicted_correctly {
1
} else {
self.branch_mispredict_penalty
}
}
}
#[derive(Debug, Clone)]
pub struct X86IndirectBranchTracking {
pub enabled: bool,
pub num_endbr_inserted: usize,
pub track_indirect_calls: bool,
pub track_indirect_jumps: bool,
}
impl X86IndirectBranchTracking {
pub fn new(enabled: bool) -> Self {
Self {
enabled,
num_endbr_inserted: 0,
track_indirect_calls: true,
track_indirect_jumps: true,
}
}
pub fn needs_endbr(&self, is_indirect_target: bool) -> bool {
self.enabled && is_indirect_target
}
pub fn insert_endbr(&mut self, is_64bit: bool) -> Vec<u8> {
self.num_endbr_inserted += 1;
if is_64bit {
vec![0xF3, 0x0F, 0x1E, 0xFA]
} else {
vec![0xF3, 0x0F, 0x1E, 0xFB]
}
}
pub fn is_valid_target(&self, has_endbr: bool) -> bool {
!self.enabled || has_endbr
}
pub fn has_shadow_stack(&self) -> bool {
self.enabled
}
}
#[derive(Debug, Clone)]
pub struct X86StackProtector {
pub enabled: bool,
pub canary_offset: u32,
pub num_protected: usize,
}
impl X86StackProtector {
pub fn new(enabled: bool, is_64bit: bool) -> Self {
Self {
enabled,
canary_offset: if is_64bit { 0x28 } else { 0x14 },
num_protected: 0,
}
}
pub fn get_canary_address(&self, is_64bit: bool) -> (String, u32) {
if is_64bit {
("fs".to_string(), self.canary_offset)
} else {
("gs".to_string(), self.canary_offset)
}
}
pub fn needs_protection(&self, has_buffer: bool, has_alloca: bool) -> bool {
self.enabled && (has_buffer || has_alloca)
}
pub fn insert_prologue_check(&mut self) -> Vec<String> {
self.num_protected += 1;
vec![
"mov rax, fs:0x28".to_string(),
"mov [rbp-8], rax".to_string(),
]
}
pub fn insert_epilogue_check(&self) -> Vec<String> {
vec![
"mov rax, [rbp-8]".to_string(),
"xor rax, fs:0x28".to_string(),
"jne __stack_chk_fail".to_string(),
]
}
}
#[derive(Debug, Clone)]
pub struct X86RetpolineThunk {
pub enabled: bool,
pub num_thunks: usize,
pub thunk_size: u32,
}
impl X86RetpolineThunk {
pub fn new(enabled: bool) -> Self {
Self {
enabled,
num_thunks: 0,
thunk_size: 21,
}
}
pub fn generate_call_thunk(&mut self, _target_reg: u32, is_64bit: bool) -> Vec<u8> {
self.num_thunks += 1;
if is_64bit {
vec![
0xE8, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x90, 0x0F, 0xAE, 0xE8, 0xEB, 0xF6, 0x48, 0x89,
0x04, 0x24, 0xC3,
]
} else {
vec![
0xE8, 0x00, 0x00, 0x00, 0x00, 0xF3, 0x90, 0x0F, 0xAE, 0xE8, 0xEB, 0xF6, 0x87, 0x04,
0x24, 0xC3,
]
}
}
pub fn generate_jump_thunk(&mut self, is_64bit: bool) -> Vec<u8> {
self.generate_call_thunk(0, is_64bit)
}
pub fn needs_retpoline(&self, is_indirect: bool) -> bool {
self.enabled && is_indirect
}
}
#[cfg(test)]
mod comprehensive_tests {
use super::*;
#[test]
fn test_load_store_optimizer_creation() {
let opt = X86LoadStoreOptimizer::new();
assert_eq!(opt.num_combined_loads, 0);
assert_eq!(opt.num_combined_stores, 0);
}
#[test]
fn test_can_combine_loads_adjacent() {
assert!(X86LoadStoreOptimizer::can_combine_loads(0, 4, 4, 4));
assert!(X86LoadStoreOptimizer::can_combine_loads(0, 8, 8, 8));
}
#[test]
fn test_cannot_combine_loads_gap() {
assert!(!X86LoadStoreOptimizer::can_combine_loads(0, 8, 4, 4));
}
#[test]
fn test_cannot_combine_loads_different_sizes() {
assert!(!X86LoadStoreOptimizer::can_combine_loads(0, 4, 4, 8));
}
#[test]
fn test_combined_size() {
assert_eq!(X86LoadStoreOptimizer::combined_size(4), 8);
assert_eq!(X86LoadStoreOptimizer::combined_size(8), 16);
}
#[test]
fn test_try_combine_loads() {
let mut opt = X86LoadStoreOptimizer::new();
let loads = vec![(0, 4), (4, 4), (16, 8), (24, 8)];
let combined = opt.try_combine_loads(&loads);
assert_eq!(combined.len(), 2);
assert_eq!(opt.num_combined_loads, 2);
}
#[test]
fn test_try_combine_stores() {
let mut opt = X86LoadStoreOptimizer::new();
let stores = vec![(100, 4), (104, 4), (200, 4)];
let combined = opt.try_combine_stores(&stores);
assert_eq!(combined.len(), 2);
assert_eq!(opt.num_combined_stores, 1);
}
#[test]
fn test_is_always_beneficial() {
assert!(X86LoadStoreOptimizer::is_always_beneficial(8));
assert!(X86LoadStoreOptimizer::is_always_beneficial(32));
assert!(!X86LoadStoreOptimizer::is_always_beneficial(64));
}
#[test]
fn test_lzcnt_opt_with_lzcnt() {
let features = X86TargetFeatures {
has_lzcnt: true,
has_bmi: true,
};
let opt = X86LZCNTBSFOpt::new(&features);
assert_eq!(opt.select_clz_impl(), X86CLZImpl::LZCNT);
assert_eq!(opt.select_ctz_impl(), X86CTZImpl::TZCNT);
}
#[test]
fn test_lzcnt_opt_without_lzcnt() {
let features = X86TargetFeatures::default();
let opt = X86LZCNTBSFOpt::new(&features);
assert_eq!(opt.select_clz_impl(), X86CLZImpl::BSR);
assert_eq!(opt.select_ctz_impl(), X86CTZImpl::BSF);
}
#[test]
fn test_lzcnt_opt_bmi1_only() {
let features = X86TargetFeatures {
has_lzcnt: false,
has_bmi: true,
};
let opt = X86LZCNTBSFOpt::new(&features);
assert_eq!(opt.select_clz_impl(), X86CLZImpl::BSR);
assert_eq!(opt.select_ctz_impl(), X86CTZImpl::BLSI);
}
#[test]
fn test_needs_zero_fixup() {
let features = X86TargetFeatures::default();
let opt = X86LZCNTBSFOpt::new(&features);
assert!(!opt.needs_zero_fixup(X86CLZImpl::LZCNT));
assert!(opt.needs_zero_fixup(X86CLZImpl::BSR));
}
#[test]
fn test_zero_fixup_generation() {
let fixup = X86LZCNTBSFOpt::generate_zero_fixup();
assert_eq!(fixup.len(), 2);
}
#[test]
fn test_lzcnt_tzcnt_encode_available() {
let f = X86TargetFeatures {
has_lzcnt: true,
has_bmi: true,
};
let opt = X86LZCNTBSFOpt::new(&f);
assert!(opt.can_encode_lzcnt());
assert!(opt.can_encode_tzcnt());
}
#[test]
fn test_lzcnt_tzcnt_encode_unavailable() {
let opt = X86LZCNTBSFOpt::new(&X86TargetFeatures::default());
assert!(!opt.can_encode_lzcnt());
assert!(!opt.can_encode_tzcnt());
}
#[test]
fn test_blsi_for_ctz() {
let f = X86TargetFeatures { has_bmi: true };
assert!(X86LZCNTBSFOpt::new(&f).can_use_blsi_for_ctz());
}
#[test]
fn test_sched_model_skylake() {
let m = X86SchedModelInfo::new(X86MicroArch::Skylake);
assert_eq!(m.issue_width, 4);
assert_eq!(m.alu_latency(), 1);
assert_eq!(m.imul_latency(), 3);
}
#[test]
fn test_sched_model_icelake() {
let m = X86SchedModelInfo::new(X86MicroArch::IceLake);
assert_eq!(m.issue_width, 5);
}
#[test]
fn test_sched_model_zen3() {
let m = X86SchedModelInfo::new(X86MicroArch::Zen3);
assert_eq!(m.issue_width, 6);
assert_eq!(m.load_latency, 4);
}
#[test]
fn test_idiv_latency() {
let m = X86SchedModelInfo::new(X86MicroArch::Skylake);
assert!(m.idiv_latency(32) > 10);
assert!(m.idiv_latency(64) > m.idiv_latency(32));
}
#[test]
fn test_macro_fusion_benefit() {
assert!(X86SchedModelInfo::new(X86MicroArch::Skylake).macro_fusion_benefit());
}
#[test]
fn test_taken_branch_cost() {
let m = X86SchedModelInfo::new(X86MicroArch::Skylake);
assert_eq!(m.taken_branch_cost(true), 1);
assert_eq!(m.taken_branch_cost(false), 16);
}
#[test]
fn test_indirect_branch_tracking_creation() {
let ibt = X86IndirectBranchTracking::new(true);
assert!(ibt.enabled);
assert!(ibt.track_indirect_calls);
}
#[test]
fn test_needs_endbr() {
let ibt = X86IndirectBranchTracking::new(true);
assert!(ibt.needs_endbr(true));
assert!(!ibt.needs_endbr(false));
}
#[test]
fn test_needs_endbr_disabled() {
assert!(!X86IndirectBranchTracking::new(false).needs_endbr(true));
}
#[test]
fn test_insert_endbr64() {
let mut ibt = X86IndirectBranchTracking::new(true);
assert_eq!(ibt.insert_endbr(true), vec![0xF3, 0x0F, 0x1E, 0xFA]);
assert_eq!(ibt.num_endbr_inserted, 1);
}
#[test]
fn test_insert_endbr32() {
let mut ibt = X86IndirectBranchTracking::new(true);
assert_eq!(ibt.insert_endbr(false), vec![0xF3, 0x0F, 0x1E, 0xFB]);
}
#[test]
fn test_is_valid_target() {
let ibt = X86IndirectBranchTracking::new(true);
assert!(ibt.is_valid_target(true));
assert!(!ibt.is_valid_target(false));
}
#[test]
fn test_is_valid_target_disabled() {
assert!(X86IndirectBranchTracking::new(false).is_valid_target(false));
}
#[test]
fn test_has_shadow_stack() {
assert!(X86IndirectBranchTracking::new(true).has_shadow_stack());
assert!(!X86IndirectBranchTracking::new(false).has_shadow_stack());
}
#[test]
fn test_stack_protector_64bit() {
let sp = X86StackProtector::new(true, true);
assert_eq!(sp.canary_offset, 0x28);
}
#[test]
fn test_stack_protector_32bit() {
let sp = X86StackProtector::new(true, false);
assert_eq!(sp.canary_offset, 0x14);
}
#[test]
fn test_get_canary_address_64() {
let sp = X86StackProtector::new(true, true);
let (seg, off) = sp.get_canary_address(true);
assert_eq!(seg, "fs");
assert_eq!(off, 0x28);
}
#[test]
fn test_get_canary_address_32() {
let sp = X86StackProtector::new(true, false);
let (seg, off) = sp.get_canary_address(false);
assert_eq!(seg, "gs");
assert_eq!(off, 0x14);
}
#[test]
fn test_needs_protection() {
let sp = X86StackProtector::new(true, true);
assert!(sp.needs_protection(true, false));
assert!(sp.needs_protection(false, true));
assert!(!sp.needs_protection(false, false));
}
#[test]
fn test_needs_protection_disabled() {
assert!(!X86StackProtector::new(false, true).needs_protection(true, true));
}
#[test]
fn test_insert_prologue_check() {
let mut sp = X86StackProtector::new(true, true);
let checks = sp.insert_prologue_check();
assert_eq!(checks.len(), 2);
assert_eq!(sp.num_protected, 1);
}
#[test]
fn test_insert_epilogue_check() {
let sp = X86StackProtector::new(true, true);
let checks = sp.insert_epilogue_check();
assert_eq!(checks.len(), 3);
assert!(checks[2].contains("__stack_chk_fail"));
}
#[test]
fn test_retpoline_creation() {
let rt = X86RetpolineThunk::new(true);
assert!(rt.enabled);
assert_eq!(rt.num_thunks, 0);
}
#[test]
fn test_generate_call_thunk_64() {
let mut rt = X86RetpolineThunk::new(true);
let bytes = rt.generate_call_thunk(0, true);
assert!(!bytes.is_empty());
assert_eq!(rt.num_thunks, 1);
}
#[test]
fn test_generate_call_thunk_32() {
let mut rt = X86RetpolineThunk::new(true);
assert!(!rt.generate_call_thunk(0, false).is_empty());
}
#[test]
fn test_generate_jump_thunk() {
let mut rt = X86RetpolineThunk::new(true);
assert!(!rt.generate_jump_thunk(true).is_empty());
assert_eq!(rt.num_thunks, 1);
}
#[test]
fn test_needs_retpoline() {
let rt = X86RetpolineThunk::new(true);
assert!(rt.needs_retpoline(true));
assert!(!rt.needs_retpoline(false));
}
#[test]
fn test_needs_retpoline_disabled() {
assert!(!X86RetpolineThunk::new(false).needs_retpoline(true));
}
#[test]
fn test_microarch_values() {
assert!(X86MicroArch::SandyBridge as u32 != X86MicroArch::Skylake as u32);
assert_eq!(X86MicroArch::Skylake, X86MicroArch::Skylake);
assert_ne!(X86MicroArch::Skylake, X86MicroArch::Zen3);
}
#[test]
fn test_abi_type_values() {
assert_eq!(X86ABIType::SystemV, X86ABIType::SystemV);
assert_ne!(X86ABIType::SystemV, X86ABIType::Microsoft);
}
#[test]
fn test_fixup_action_values() {
assert_eq!(X86FixupAction::InsertMovzx, X86FixupAction::InsertMovzx);
assert_ne!(X86FixupAction::InsertMovzx, X86FixupAction::PromoteTo32);
}
#[test]
fn test_exec_domain_values() {
assert_eq!(X86ExecDomain::Integer, X86ExecDomain::Integer);
assert_ne!(X86ExecDomain::Integer, X86ExecDomain::SsePacked);
}
#[test]
fn test_prefetch_hint_values() {
assert_eq!(X86PrefetchHint::T0, X86PrefetchHint::T0);
assert_ne!(X86PrefetchHint::T0, X86PrefetchHint::NTA);
}
#[test]
fn test_clz_impl_values() {
assert_eq!(X86CLZImpl::LZCNT, X86CLZImpl::LZCNT);
assert_ne!(X86CLZImpl::LZCNT, X86CLZImpl::BSR);
}
#[test]
fn test_ctz_impl_values() {
assert_eq!(X86CTZImpl::TZCNT, X86CTZImpl::TZCNT);
assert_ne!(X86CTZImpl::TZCNT, X86CTZImpl::BLSI);
}
#[test]
fn test_reg_class_values() {
assert_eq!(X86RegClass::GR8, X86RegClass::GR8);
assert_ne!(X86RegClass::GR8, X86RegClass::GR32);
}
#[test]
fn test_empty_addressing_fold() {
let mut opt = X86AddressingModeOptimizer::new();
let am = opt.try_fold_addressing(None, None, 1, 0).unwrap();
assert_eq!(am.base_reg, None);
}
#[test]
fn test_max_displacement() {
let opt = X86AddressingModeOptimizer::new();
assert!(X86AddressingModeOptimizer::is_disp32(opt.max_disp));
}
#[test]
fn test_frame_size_zero() {
let prep = X86FrameLoweringPrep::default();
assert_eq!(prep.frame_size, 0);
assert_eq!(prep.num_callee_saved(), 0);
}
#[test]
fn test_shrink_wrap_no_regs() {
let sw = X86ShrinkWrapPass::new();
assert!(sw.find_save_point(0).is_none());
assert!(sw.find_restore_point(0).is_none());
}
#[test]
fn test_macro_fusion_all_pairs_icelake() {
let mf = X86MacroFusion::new(X86MicroArch::IceLake);
let pairs = [
FusionPair::CmpJcc,
FusionPair::TestJcc,
FusionPair::AddJcc,
FusionPair::SubJcc,
FusionPair::AndJcc,
FusionPair::OrJcc,
FusionPair::DecJcc,
FusionPair::IncJcc,
];
for p in &pairs {
assert!(mf.supports(p));
}
}
#[test]
fn test_branch_relaxation_edge() {
assert!(X86BranchRelaxation::is_short_branch_range(-128));
assert!(X86BranchRelaxation::is_short_branch_range(127));
}
#[test]
fn test_load_store_no_combine() {
let mut opt = X86LoadStoreOptimizer::new();
let loads = vec![(0, 4), (8, 4)];
assert_eq!(opt.try_combine_loads(&loads).len(), 2);
assert_eq!(opt.num_combined_loads, 0);
}
#[test]
fn test_clflush_redundant_removal_empty() {
let mut co = X86ClflushOpt::new(false, false);
assert!(co.remove_redundant(&[]).is_empty());
}
#[test]
fn test_pad_short_zero_offset() {
let pad = X86PadShortFunctions::new(16);
assert!(X86PadShortFunctions::generate_nops(pad.compute_padding(0)).is_empty());
}
#[test]
fn test_prefetch_negative_stride() {
assert!(!X86UnrollPrefetch::new().should_prefetch(16, 0));
}
#[test]
fn test_ibt_endbr_multiple() {
let mut ibt = X86IndirectBranchTracking::new(true);
ibt.insert_endbr(true);
ibt.insert_endbr(true);
ibt.insert_endbr(true);
assert_eq!(ibt.num_endbr_inserted, 3);
}
#[test]
fn test_micro_fusion_all_alu_jcc() {
for op in &[
X86MicroFusionOp::Add,
X86MicroFusionOp::Sub,
X86MicroFusionOp::And,
X86MicroFusionOp::Or,
X86MicroFusionOp::Dec,
X86MicroFusionOp::Inc,
] {
assert!(X86MicroFusion::can_fuse(op, &X86MicroFusionOp::Jcc).is_some());
}
}
#[test]
fn test_vpack_stores() {
assert!(X86VPack::can_pack_stores(0, 4, 4, 4));
assert!(!X86VPack::can_pack_stores(0, 8, 4, 4));
}
#[test]
fn test_exec_domain_try_fix() {
let mut fix = X86ExecutionDomainFix::new();
assert!(fix.try_fix_domain(X86ExecDomain::Integer, X86ExecDomain::SsePacked));
assert_eq!(fix.num_fixed, 1);
}
#[test]
fn test_fixup_bw_promote() {
let mut fbw = X86FixupBW::new();
fbw.promote_to_32bit();
assert_eq!(fbw.num_promoted, 1);
}
#[test]
fn test_lzcnt_opt_accumulation() {
let mut opt = X86LZCNTBSFOpt::new(&X86TargetFeatures::default());
opt.num_optimized += 1;
assert_eq!(opt.num_optimized, 1);
}
#[test]
fn test_macro_fusion_count_accumulation() {
let mut mf = X86MacroFusion::new(X86MicroArch::Skylake);
assert_eq!(mf.fusion_count(), 0);
mf.try_macro_fuse(&X86MicroFusionOp::Cmp, &X86MicroFusionOp::Jcc);
assert_eq!(mf.fusion_count(), 1);
}
#[test]
fn test_modrm_encoding_roundtrip() {
let modrm = X86AddressingModeOptimizer::encode_modrm(3, 0, 0);
assert_eq!((modrm >> 6) & 0x03, 3);
assert_eq!((modrm >> 3) & 0x07, 0);
assert_eq!(modrm & 0x07, 0);
}
#[test]
fn test_sib_encoding_all_scales() {
for (scale, bits) in &[(1u8, 0u8), (2, 1), (4, 2), (8, 3)] {
let sib = X86AddressingModeOptimizer::encode_sib(*scale, 0, 0);
assert_eq!((sib >> 6) & 0x03, *bits);
}
}
#[test]
fn test_dominator_self() {
let mut sw = X86ShrinkWrapPass::new();
sw.compute_dominator_sets(&[0, 1], &[(0, 1)], 0);
assert!(sw.is_dominated_by(0, 0));
assert!(sw.is_dominated_by(1, 0));
assert!(!sw.is_dominated_by(0, 1));
}
#[test]
fn test_decompose_lea() {
let mut opt = X86AddressingModeOptimizer::new();
let am = opt.decompose_lea(0, 1, 2, 4, 16);
assert_eq!(am.base_reg, Some(1));
assert_eq!(am.index_reg, Some(2));
assert_eq!(am.scale, 4);
}
#[test]
fn test_decompose_lea_no_index() {
let mut opt = X86AddressingModeOptimizer::new();
let am = opt.decompose_lea(0, 1, 1, 0, 8);
assert_eq!(am.index_reg, None);
assert_eq!(am.scale, 1);
}
#[test]
fn test_align_frame_size_various() {
for &align in &[4u32, 8, 16, 32] {
let mut prep = X86FrameLoweringPrep::new(true, X86ABIType::SystemV);
prep.stack_alignment = align;
prep.frame_size = align as u64 + 1;
prep.align_frame_size();
assert_eq!(prep.frame_size % align as u64, 0);
}
}
#[test]
fn test_instr_combine_xor_idiom_tracking() {
let mut c = X86InstrCombiner::new();
let instr = MachineInstr {
opcode: 8,
operands: vec![
MachineOperand::Reg(1),
MachineOperand::Reg(1),
MachineOperand::Reg(1),
],
..Default::default()
};
assert!(c.try_combine_single(&instr).is_some());
assert_eq!(c.stats.xor_zero_idioms, 1);
}
#[test]
fn test_instr_combine_cmp_to_test_tracking() {
let mut c = X86InstrCombiner::new();
let instr = MachineInstr {
opcode: 18,
operands: vec![
MachineOperand::Reg(1),
MachineOperand::Reg(2),
MachineOperand::Imm(0),
],
..Default::default()
};
assert!(c.try_combine_single(&instr).is_some());
assert_eq!(c.stats.cmp_to_test, 1);
}
#[test]
fn test_frame_lowering_all_abi_types() {
for abi in &[
X86ABIType::SystemV,
X86ABIType::Microsoft,
X86ABIType::Cdecl,
X86ABIType::Stdcall,
X86ABIType::Fastcall,
X86ABIType::Thiscall,
X86ABIType::Vectorcall,
X86ABIType::Regcall,
] {
let prep = X86FrameLoweringPrep::new(true, *abi);
assert!(prep.frame_size < 100000);
}
}
#[test]
fn test_loop_prefetch_various() {
let up = X86UnrollPrefetch::new();
let d1 = up.compute_prefetch_distance(4, 8);
let d2 = up.compute_prefetch_distance(16, 8);
assert!(d1 <= d2);
}
#[test]
fn test_transform_stats_all_zero() {
let s = X86TransformStats::default();
assert_eq!(s.dag_combines, 0);
assert_eq!(s.instr_combines, 0);
assert_eq!(s.type_legalizations, 0);
assert_eq!(s.op_legalizations, 0);
assert_eq!(s.lea_formations, 0);
assert_eq!(s.zero_idioms, 0);
assert_eq!(s.vector_scalarizes, 0);
assert_eq!(s.libcall_generations, 0);
}
#[test]
fn test_addressing_mode_default() {
let am = X86AddressingMode::default();
assert_eq!(am.scale, 1);
assert_eq!(am.displacement, 0);
}
#[test]
fn test_switch_lowering_strategy_debug() {
let jt = SwitchLoweringStrategy::JumpTable {
min: 0,
max: 10,
num_entries: 11,
};
assert_eq!(format!("{:?}", jt).len() > 0, true);
}
#[test]
fn test_legalize_action_debug() {
assert_eq!(format!("{:?}", X86LegalizeAction::Legal).len() > 0, true);
}
#[test]
fn test_op_legalize_action_debug() {
assert_eq!(format!("{:?}", X86OpLegalizeAction::Legal).len() > 0, true);
}
#[test]
fn test_custom_lowering_debug() {
assert_eq!(format!("{:?}", X86CustomLowering::Default).len() > 0, true);
}
#[test]
fn test_expanded_type_debug() {
assert_eq!(
format!("{:?}", X86ExpandedType::Legal(Type::i32())).len() > 0,
true
);
}
#[test]
fn test_op_legalize_result_debug() {
assert_eq!(format!("{:?}", X86OpLegalizeResult::Legal).len() > 0, true);
}
#[test]
fn test_fusion_pair_debug() {
assert_eq!(format!("{:?}", FusionPair::CmpJcc).len() > 0, true);
}
#[test]
fn test_legalize_operation_expand_sub_i128() {
let mut l = X86OperationLegalizer::new(X86TargetFeatures::default());
let ty = Type::int(128);
let result = l.expand_operation(SDOpcode::Sub, &ty);
assert_eq!(result.len(), 2);
}
#[test]
fn test_legalize_operation_expand_mul_i128() {
let mut l = X86OperationLegalizer::new(X86TargetFeatures::default());
let ty = Type::int(128);
let result = l.expand_operation(SDOpcode::Mul, &ty);
assert_eq!(result.len(), 2);
}
#[test]
fn test_legalize_operation_expand_selectcc() {
let mut l = X86OperationLegalizer::new(X86TargetFeatures::default());
let ty = Type::i32();
let result = l.expand_operation(SDOpcode::SelectCC, &ty);
assert_eq!(result.len(), 2);
}
#[test]
fn test_generate_libcall_all() {
let mut l = X86OperationLegalizer::new(X86TargetFeatures::default());
assert_eq!(
l.generate_libcall(SDOpcode::FSin, &Type::double()),
Some("sin".to_string())
);
assert_eq!(
l.generate_libcall(SDOpcode::FCos, &Type::double()),
Some("cos".to_string())
);
assert_eq!(
l.generate_libcall(SDOpcode::FLog2, &Type::double()),
Some("log2".to_string())
);
assert_eq!(
l.generate_libcall(SDOpcode::FExp2, &Type::double()),
Some("exp2".to_string())
);
}
#[test]
fn test_generate_libcall_i128() {
let mut l = X86OperationLegalizer::new(X86TargetFeatures::default());
assert_eq!(
l.generate_libcall(SDOpcode::UDiv, &Type::int(128)),
Some("__udivti3".to_string())
);
assert_eq!(
l.generate_libcall(SDOpcode::SDiv, &Type::int(128)),
Some("__divti3".to_string())
);
assert_eq!(
l.generate_libcall(SDOpcode::URem, &Type::int(128)),
Some("__umodti3".to_string())
);
assert_eq!(
l.generate_libcall(SDOpcode::SRem, &Type::int(128)),
Some("__modti3".to_string())
);
}
#[test]
fn test_generate_libcall_none() {
let mut l = X86OperationLegalizer::new(X86TargetFeatures::default());
assert_eq!(l.generate_libcall(SDOpcode::Add, &Type::i32()), None);
}
#[test]
fn test_custom_lower_all() {
let mut l = X86OperationLegalizer::new(X86TargetFeatures::default());
assert_eq!(
l.custom_lower(SDOpcode::Rotl, &Type::i32()),
X86CustomLowering::X86RotateLeft
);
assert_eq!(
l.custom_lower(SDOpcode::Rotr, &Type::i32()),
X86CustomLowering::X86RotateRight
);
assert_eq!(
l.custom_lower(SDOpcode::BSwap, &Type::i32()),
X86CustomLowering::X86ByteSwap
);
assert_eq!(
l.custom_lower(SDOpcode::CtLz, &Type::i32()),
X86CustomLowering::X86CountLeadingZeros
);
assert_eq!(
l.custom_lower(SDOpcode::CtTz, &Type::i32()),
X86CustomLowering::X86CountTrailingZeros
);
assert_eq!(
l.custom_lower(SDOpcode::CtPop, &Type::i32()),
X86CustomLowering::X86PopCount
);
assert_eq!(
l.custom_lower(SDOpcode::UAddSat, &Type::i32()),
X86CustomLowering::X86AddSat
);
assert_eq!(
l.custom_lower(SDOpcode::SAddSat, &Type::i32()),
X86CustomLowering::X86AddSatSigned
);
assert_eq!(
l.custom_lower(SDOpcode::USubSat, &Type::i32()),
X86CustomLowering::X86SubSat
);
assert_eq!(
l.custom_lower(SDOpcode::SSubSat, &Type::i32()),
X86CustomLowering::X86SubSatSigned
);
assert_eq!(
l.custom_lower(SDOpcode::SelectCC, &Type::i32()),
X86CustomLowering::X86ConditionalMove
);
}
#[test]
fn test_is_operation_legal_basic() {
let l = X86OperationLegalizer::new(X86TargetFeatures::default());
let ty = Type::i32();
assert!(l.is_operation_legal(SDOpcode::Add, &ty, &[ty.clone(), ty.clone()]));
assert!(l.is_operation_legal(SDOpcode::Sub, &ty, &[ty.clone(), ty.clone()]));
assert!(l.is_operation_legal(SDOpcode::Mul, &ty, &[ty.clone(), ty.clone()]));
}
#[test]
fn test_is_operation_legal_fma() {
let l_fma = X86OperationLegalizer::new(X86TargetFeatures { has_fma: true });
let l_no_fma = X86OperationLegalizer::new(X86TargetFeatures { has_fma: false });
let ty = Type::double();
let ops = vec![ty.clone(), ty.clone(), ty.clone()];
assert!(l_fma.is_operation_legal(SDOpcode::FMA, &ty, &ops));
assert!(!l_no_fma.is_operation_legal(SDOpcode::FMA, &ty, &ops));
}
#[test]
fn test_type_legalizer_get_num_registers_all() {
let l = X86TypeLegalizer::new(X86TargetFeatures::default());
assert_eq!(l.get_num_registers(&Type::i1()), 1);
assert_eq!(l.get_num_registers(&Type::i32()), 1);
assert_eq!(l.get_num_registers(&Type::i64()), 1);
assert_eq!(l.get_num_registers(&Type::int(128)), 2);
assert_eq!(l.get_num_registers(&Type::float()), 1);
assert_eq!(l.get_num_registers(&Type::double()), 1);
assert_eq!(l.get_num_registers(&Type::fp128()), 2);
}
#[test]
fn test_feature_matrix_with_operations() {
for &(fma, popcnt, lzcnt) in &[
(false, false, false),
(true, false, false),
(true, true, false),
(true, true, true),
] {
let f = X86TargetFeatures {
has_fma: fma,
has_popcnt: popcnt,
has_lzcnt: lzcnt,
};
let l = X86OperationLegalizer::new(f);
let ty = Type::double();
let ops = vec![ty.clone(), ty.clone(), ty.clone()];
let action = l.get_operation_action(SDOpcode::FMA, &ty, &ops);
if fma {
assert_eq!(action, X86OpLegalizeAction::Legal);
} else {
assert_eq!(action, X86OpLegalizeAction::Expand);
}
}
}
#[test]
fn test_type_legalize_action_clone() {
let a = X86LegalizeAction::Legal;
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn test_op_expanded_type_clone() {
let a = X86ExpandedType::Legal(Type::i32());
let _b = a.clone();
}
#[test]
fn test_op_legalize_result_clone_variants() {
for r in &[
X86OpLegalizeResult::Legal,
X86OpLegalizeResult::Expanded(vec![]),
X86OpLegalizeResult::Promoted(X86PromotedOp {
promoted_ty: Type::i32(),
original_op: SDOpcode::Add,
}),
X86OpLegalizeResult::Libcall(None),
X86OpLegalizeResult::Custom(X86CustomLowering::Default),
] {
let _c = r.clone();
}
}
}
#[derive(Debug, Clone)]
pub struct X86CondBrFolding {
pub num_folded: usize,
pub num_reversed: usize,
pub num_removed: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrFoldResult {
Unconditional,
Removed,
Reversed,
Unchanged,
}
impl X86CondBrFolding {
pub fn new() -> Self {
Self {
num_folded: 0,
num_reversed: 0,
num_removed: 0,
}
}
pub fn try_fold_constant_condition(condition_is_true: Option<bool>) -> BrFoldResult {
match condition_is_true {
Some(true) => BrFoldResult::Unconditional,
Some(false) => BrFoldResult::Removed,
None => BrFoldResult::Unchanged,
}
}
pub fn target_is_fallthrough(target: usize, fallthrough: usize) -> bool {
target == fallthrough
}
pub fn reverse_branch(&mut self) {
self.num_reversed += 1;
}
pub fn analyze_branch(
&mut self,
cond_known: Option<bool>,
target: usize,
fallthrough: usize,
false_target: usize,
) -> BrFoldResult {
if let Some(is_true) = cond_known {
let result = Self::try_fold_constant_condition(Some(is_true));
self.num_folded += 1;
return result;
}
if Self::target_is_fallthrough(target, fallthrough) {
self.num_removed += 1;
return BrFoldResult::Removed;
}
if Self::target_is_fallthrough(false_target, fallthrough) && target != fallthrough {
self.num_reversed += 1;
return BrFoldResult::Reversed;
}
BrFoldResult::Unchanged
}
pub fn total_optimizations(&self) -> usize {
self.num_folded + self.num_reversed + self.num_removed
}
}
#[derive(Debug, Clone)]
pub struct X86LoopAlignment {
pub alignment: u32,
pub max_nop_bytes: u32,
pub num_aligned: usize,
}
impl X86LoopAlignment {
pub fn new(alignment: u32) -> Self {
Self {
alignment,
max_nop_bytes: 15,
num_aligned: 0,
}
}
pub fn compute_optimal_alignment(loop_size: u32, trip_count: u64, is_hot: bool) -> u32 {
if !is_hot && trip_count < 8 {
return 1;
}
match loop_size {
0..=16 => 16,
17..=64 => 16.max(if trip_count > 100 { 32 } else { 16 }),
65..=128 => 16,
_ => 8,
}
}
pub fn should_align(&self, loop_size: u32, trip_count: u64, is_hot: bool) -> bool {
self.alignment > 1 && Self::compute_optimal_alignment(loop_size, trip_count, is_hot) >= 16
}
pub fn bytes_needed_for_alignment(&self, current_offset: u32) -> u32 {
let rem = current_offset % self.alignment;
if rem == 0 {
0
} else {
self.alignment - rem
}
}
}
#[derive(Debug, Clone)]
pub struct X86TLSOptimizer {
pub num_gd_to_ie: usize,
pub num_ie_to_le: usize,
pub num_ld_to_le: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XLSTLSModel {
GeneralDynamic,
LocalDynamic,
InitialExec,
LocalExec,
}
impl X86TLSOptimizer {
pub fn new() -> Self {
Self {
num_gd_to_ie: 0,
num_ie_to_le: 0,
num_ld_to_le: 0,
}
}
pub fn can_relax_model(from: XLSTLSModel, to: XLSTLSModel, is_local_exec: bool) -> bool {
match (from, to) {
(XLSTLSModel::GeneralDynamic, XLSTLSModel::InitialExec) => true,
(XLSTLSModel::GeneralDynamic, XLSTLSModel::LocalExec) => is_local_exec,
(XLSTLSModel::LocalDynamic, XLSTLSModel::LocalExec) => is_local_exec,
(XLSTLSModel::InitialExec, XLSTLSModel::LocalExec) => is_local_exec,
_ => false,
}
}
pub fn relax_model(&mut self, from: XLSTLSModel, to: XLSTLSModel) {
match (from, to) {
(XLSTLSModel::GeneralDynamic, XLSTLSModel::InitialExec) => self.num_gd_to_ie += 1,
(XLSTLSModel::InitialExec, XLSTLSModel::LocalExec) => self.num_ie_to_le += 1,
(XLSTLSModel::LocalDynamic, XLSTLSModel::LocalExec) => self.num_ld_to_le += 1,
_ => {}
}
}
pub fn get_minimum_model(is_pic: bool, is_local: bool) -> XLSTLSModel {
if !is_pic && is_local {
XLSTLSModel::LocalExec
} else if is_pic && is_local {
XLSTLSModel::InitialExec
} else {
XLSTLSModel::GeneralDynamic
}
}
pub fn seq_size(model: XLSTLSModel, is_64bit: bool) -> u32 {
match (model, is_64bit) {
(XLSTLSModel::GeneralDynamic, true) => 24,
(XLSTLSModel::LocalDynamic, true) => 20,
(XLSTLSModel::InitialExec, true) => 10,
(XLSTLSModel::LocalExec, true) => 8,
(XLSTLSModel::GeneralDynamic, false) => 16,
(XLSTLSModel::LocalDynamic, false) => 14,
(XLSTLSModel::InitialExec, false) => 8,
(XLSTLSModel::LocalExec, false) => 6,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ConstantPoolOptimizer {
pub num_merged: usize,
pub num_deduplicated: usize,
pub pool_size: u64,
constants: HashMap<(u64, u64), u64>,
}
impl X86ConstantPoolOptimizer {
pub fn new() -> Self {
Self {
num_merged: 0,
num_deduplicated: 0,
pool_size: 0,
constants: HashMap::new(),
}
}
pub fn add_constant(&mut self, val: u64, ty_key: u64) -> Option<u64> {
let key = (val, ty_key);
if let Some(&existing) = self.constants.get(&key) {
self.num_deduplicated += 1;
Some(existing)
} else {
let offset = self.pool_size;
self.constants.insert(key, offset);
self.pool_size += 8;
Some(offset)
}
}
pub fn can_share_constants(val1: u64, val2: u64) -> bool {
val1 == val2
}
pub fn merge_pools(&mut self, other: &Self) {
for (&key, &_offset) in &other.constants {
if !self.constants.contains_key(&key) {
let new_offset = self.pool_size;
self.constants.insert(key, new_offset);
self.pool_size += 8;
self.num_merged += 1;
} else {
self.num_deduplicated += 1;
}
}
}
pub fn num_constants(&self) -> usize {
self.constants.len()
}
pub fn is_empty(&self) -> bool {
self.constants.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct X86EmergencySpillSlot {
pub slot_offset: i64,
pub slot_size: u32,
pub is_used: bool,
pub reg_class: X86RegClass,
}
impl X86EmergencySpillSlot {
pub fn new(offset: i64, size: u32, reg_class: X86RegClass) -> Self {
Self {
slot_offset: offset,
slot_size: size,
is_used: false,
reg_class,
}
}
pub fn mark_used(&mut self) {
self.is_used = true;
}
pub fn is_compatible(&self, reg_class: X86RegClass, size: u32) -> bool {
self.reg_class == reg_class && self.slot_size >= size
}
pub fn compute_slot_count(num_gprs: usize, num_xmms: usize) -> usize {
(num_gprs + num_xmms + 1).min(4)
}
}
#[derive(Debug, Clone)]
pub struct X86MachineVerifierCustom {
pub errors: Vec<String>,
pub warnings: Vec<String>,
pub fatal_count: usize,
}
impl X86MachineVerifierCustom {
pub fn new() -> Self {
Self {
errors: Vec::new(),
warnings: Vec::new(),
fatal_count: 0,
}
}
pub fn verify_register_constraints(&mut self, opcode: X86Opcode, reg: u32, is_dst: bool) {
match opcode {
X86Opcode::MUL | X86Opcode::IMUL | X86Opcode::DIV | X86Opcode::IDIV => {
if is_dst {
self.warnings
.push(format!("MUL/DIV implicit destination check for r{}", reg));
}
}
X86Opcode::SHL | X86Opcode::SHR | X86Opcode::SAR => {
if !is_dst {
self.warnings
.push(format!("Shift amount must be CL or immediate for r{}", reg));
}
}
_ => {}
}
}
pub fn verify_eflags_liveness(&mut self, _defined: bool, _used: bool, _killed: bool) {
}
pub fn verify_stack_alignment(&mut self, stack_offset: i64, required_align: u32) {
if stack_offset % required_align as i64 != 0 {
self.errors.push(format!(
"Stack misaligned: offset {}, required alignment {}",
stack_offset, required_align
));
self.fatal_count += 1;
}
}
pub fn verify_red_zone(&mut self, access_offset: i64, red_zone_size: u64) {
if access_offset < 0 && (-access_offset as u64) <= red_zone_size {
self.warnings.push("Red zone access detected".to_string());
}
}
pub fn has_errors(&self) -> bool {
!self.errors.is_empty()
}
pub fn has_fatal(&self) -> bool {
self.fatal_count > 0
}
pub fn num_warnings(&self) -> usize {
self.warnings.len()
}
}
#[derive(Debug, Clone)]
pub struct X86CodeEmitterPrep {
pub num_fixups: usize,
pub num_relaxed: usize,
pub estimated_code_size: u64,
}
#[derive(Debug, Clone)]
pub struct X86Fixup {
pub offset: u64,
pub target: String,
pub kind: X86FixupKind,
pub addend: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86FixupKind {
Rel32,
PCRel32,
PCRel8,
Abs32,
Abs64,
GOTPCREL,
PLT32,
TlsGd,
TlsLd,
TlsIe,
TlsLe,
}
impl X86CodeEmitterPrep {
pub fn new() -> Self {
Self {
num_fixups: 0,
num_relaxed: 0,
estimated_code_size: 0,
}
}
pub fn compute_instruction_size(
_opcode: X86Opcode,
has_rex: bool,
has_modrm: bool,
has_sib: bool,
disp_size: u8,
imm_size: u8,
) -> u32 {
let mut size: u32 = 1; if has_rex {
size += 1;
}
if has_modrm {
size += 1;
}
if has_sib {
size += 1;
}
size += disp_size as u32;
size += imm_size as u32;
size
}
pub fn add_fixup(&mut self, fixup: X86Fixup) {
self.num_fixups += 1;
let _ = fixup;
}
pub fn needs_relaxation(&self, instr_size: u32, max_size: u32) -> bool {
instr_size > max_size
}
pub fn relax_instruction(&mut self) {
self.num_relaxed += 1;
}
pub fn estimate_total_size(&mut self, num_instructions: usize, avg_size: u32) {
self.estimated_code_size = num_instructions as u64 * avg_size as u64;
}
pub fn reserve_code_buffer(&self) -> u64 {
(self.estimated_code_size as f64 * 1.2) as u64 }
}
#[cfg(test)]
mod final_tests {
use super::*;
#[test]
fn test_cond_br_creation() {
let cbf = X86CondBrFolding::new();
assert_eq!(cbf.num_folded, 0);
assert_eq!(cbf.total_optimizations(), 0);
}
#[test]
fn test_fold_constant_true() {
assert_eq!(
X86CondBrFolding::try_fold_constant_condition(Some(true)),
BrFoldResult::Unconditional
);
}
#[test]
fn test_fold_constant_false() {
assert_eq!(
X86CondBrFolding::try_fold_constant_condition(Some(false)),
BrFoldResult::Removed
);
}
#[test]
fn test_fold_constant_unknown() {
assert_eq!(
X86CondBrFolding::try_fold_constant_condition(None),
BrFoldResult::Unchanged
);
}
#[test]
fn test_target_is_fallthrough() {
assert!(X86CondBrFolding::target_is_fallthrough(5, 5));
assert!(!X86CondBrFolding::target_is_fallthrough(5, 6));
}
#[test]
fn test_reverse_branch() {
let mut cbf = X86CondBrFolding::new();
cbf.reverse_branch();
assert_eq!(cbf.num_reversed, 1);
}
#[test]
fn test_analyze_branch_known_true() {
let mut cbf = X86CondBrFolding::new();
let r = cbf.analyze_branch(Some(true), 1, 2, 2);
assert_eq!(r, BrFoldResult::Unconditional);
assert_eq!(cbf.num_folded, 1);
}
#[test]
fn test_analyze_branch_known_false() {
let mut cbf = X86CondBrFolding::new();
let r = cbf.analyze_branch(Some(false), 1, 2, 2);
assert_eq!(r, BrFoldResult::Removed);
}
#[test]
fn test_analyze_branch_target_fallthrough() {
let mut cbf = X86CondBrFolding::new();
let r = cbf.analyze_branch(None, 2, 2, 1);
assert_eq!(r, BrFoldResult::Removed);
}
#[test]
fn test_analyze_branch_false_fallthrough() {
let mut cbf = X86CondBrFolding::new();
let r = cbf.analyze_branch(None, 1, 3, 3);
assert_eq!(r, BrFoldResult::Reversed);
}
#[test]
fn test_analyze_branch_unchanged() {
let mut cbf = X86CondBrFolding::new();
let r = cbf.analyze_branch(None, 1, 2, 3);
assert_eq!(r, BrFoldResult::Unchanged);
}
#[test]
fn test_loop_alignment_creation() {
let la = X86LoopAlignment::new(16);
assert_eq!(la.alignment, 16);
assert_eq!(la.num_aligned, 0);
}
#[test]
fn test_compute_optimal_alignment_hot() {
assert!(X86LoopAlignment::compute_optimal_alignment(16, 1000, true) >= 16);
}
#[test]
fn test_compute_optimal_alignment_cold() {
assert_eq!(X86LoopAlignment::compute_optimal_alignment(16, 2, false), 1);
}
#[test]
fn test_should_align() {
let la = X86LoopAlignment::new(16);
assert!(la.should_align(16, 1000, true));
assert!(!la.should_align(16, 2, false));
}
#[test]
fn test_bytes_needed_for_alignment() {
let la = X86LoopAlignment::new(16);
assert_eq!(la.bytes_needed_for_alignment(0), 0);
assert_eq!(la.bytes_needed_for_alignment(1), 15);
assert_eq!(la.bytes_needed_for_alignment(16), 0);
}
#[test]
fn test_tls_optimizer_creation() {
let tls = X86TLSOptimizer::new();
assert_eq!(tls.num_gd_to_ie, 0);
assert_eq!(tls.num_ie_to_le, 0);
assert_eq!(tls.num_ld_to_le, 0);
}
#[test]
fn test_can_relax_model() {
assert!(X86TLSOptimizer::can_relax_model(
XLSTLSModel::GeneralDynamic,
XLSTLSModel::InitialExec,
false
));
assert!(X86TLSOptimizer::can_relax_model(
XLSTLSModel::InitialExec,
XLSTLSModel::LocalExec,
true
));
assert!(X86TLSOptimizer::can_relax_model(
XLSTLSModel::GeneralDynamic,
XLSTLSModel::LocalExec,
true
));
assert!(!X86TLSOptimizer::can_relax_model(
XLSTLSModel::InitialExec,
XLSTLSModel::LocalExec,
false
));
}
#[test]
fn test_relax_model() {
let mut tls = X86TLSOptimizer::new();
tls.relax_model(XLSTLSModel::GeneralDynamic, XLSTLSModel::InitialExec);
assert_eq!(tls.num_gd_to_ie, 1);
tls.relax_model(XLSTLSModel::InitialExec, XLSTLSModel::LocalExec);
assert_eq!(tls.num_ie_to_le, 1);
tls.relax_model(XLSTLSModel::LocalDynamic, XLSTLSModel::LocalExec);
assert_eq!(tls.num_ld_to_le, 1);
}
#[test]
fn test_get_minimum_model() {
assert_eq!(
X86TLSOptimizer::get_minimum_model(false, true),
XLSTLSModel::LocalExec
);
assert_eq!(
X86TLSOptimizer::get_minimum_model(true, true),
XLSTLSModel::InitialExec
);
assert_eq!(
X86TLSOptimizer::get_minimum_model(true, false),
XLSTLSModel::GeneralDynamic
);
}
#[test]
fn test_tls_seq_size() {
assert!(
X86TLSOptimizer::seq_size(XLSTLSModel::GeneralDynamic, true)
> X86TLSOptimizer::seq_size(XLSTLSModel::LocalExec, true)
);
assert!(
X86TLSOptimizer::seq_size(XLSTLSModel::GeneralDynamic, false)
> X86TLSOptimizer::seq_size(XLSTLSModel::LocalExec, false)
);
}
#[test]
fn test_constant_pool_creation() {
let cp = X86ConstantPoolOptimizer::new();
assert!(cp.is_empty());
assert_eq!(cp.num_constants(), 0);
}
#[test]
fn test_add_constant() {
let mut cp = X86ConstantPoolOptimizer::new();
let off = cp.add_constant(42, 1);
assert!(off.is_some());
assert_eq!(cp.num_constants(), 1);
assert!(!cp.is_empty());
}
#[test]
fn test_deduplicate_constant() {
let mut cp = X86ConstantPoolOptimizer::new();
let off1 = cp.add_constant(42, 1);
let off2 = cp.add_constant(42, 1);
assert_eq!(off1, off2);
assert_eq!(cp.num_deduplicated, 1);
assert_eq!(cp.num_constants(), 1);
}
#[test]
fn test_can_share_constants() {
assert!(X86ConstantPoolOptimizer::can_share_constants(42, 42));
assert!(!X86ConstantPoolOptimizer::can_share_constants(42, 43));
}
#[test]
fn test_merge_pools() {
let mut cp1 = X86ConstantPoolOptimizer::new();
cp1.add_constant(10, 1);
let mut cp2 = X86ConstantPoolOptimizer::new();
cp2.add_constant(20, 1);
cp2.add_constant(10, 1);
cp1.merge_pools(&cp2);
assert_eq!(cp1.num_constants(), 2);
}
#[test]
fn test_emergency_spill_slot_creation() {
let slot = X86EmergencySpillSlot::new(-8, 8, X86RegClass::GR64);
assert_eq!(slot.slot_offset, -8);
assert_eq!(slot.slot_size, 8);
assert!(!slot.is_used);
}
#[test]
fn test_mark_used() {
let mut slot = X86EmergencySpillSlot::new(-8, 8, X86RegClass::GR64);
slot.mark_used();
assert!(slot.is_used);
}
#[test]
fn test_is_compatible() {
let slot = X86EmergencySpillSlot::new(-8, 8, X86RegClass::GR64);
assert!(slot.is_compatible(X86RegClass::GR64, 8));
assert!(slot.is_compatible(X86RegClass::GR64, 4));
assert!(!slot.is_compatible(X86RegClass::GR32, 8));
assert!(!slot.is_compatible(X86RegClass::GR64, 16));
}
#[test]
fn test_slot_count() {
let count = X86EmergencySpillSlot::compute_slot_count(2, 1);
assert_eq!(count, 4.min(4));
let count2 = X86EmergencySpillSlot::compute_slot_count(0, 0);
assert_eq!(count2, 1);
}
#[test]
fn test_machine_verifier_creation() {
let v = X86MachineVerifierCustom::new();
assert!(!v.has_errors());
assert!(!v.has_fatal());
assert_eq!(v.num_warnings(), 0);
}
#[test]
fn test_verify_register_constraints() {
let mut v = X86MachineVerifierCustom::new();
v.verify_register_constraints(X86Opcode::MUL, 0, true);
assert!(v.num_warnings() > 0);
}
#[test]
fn test_verify_stack_alignment_ok() {
let mut v = X86MachineVerifierCustom::new();
v.verify_stack_alignment(16, 16);
assert!(!v.has_errors());
}
#[test]
fn test_verify_stack_alignment_bad() {
let mut v = X86MachineVerifierCustom::new();
v.verify_stack_alignment(15, 16);
assert!(v.has_errors());
assert!(v.has_fatal());
}
#[test]
fn test_verify_red_zone() {
let mut v = X86MachineVerifierCustom::new();
v.verify_red_zone(-8, 128);
assert!(v.num_warnings() > 0);
}
#[test]
fn test_code_emitter_prep_creation() {
let prep = X86CodeEmitterPrep::new();
assert_eq!(prep.num_fixups, 0);
assert_eq!(prep.num_relaxed, 0);
assert_eq!(prep.estimated_code_size, 0);
}
#[test]
fn test_compute_instruction_size() {
let size =
X86CodeEmitterPrep::compute_instruction_size(X86Opcode::ADD, true, true, false, 4, 4);
assert!(size > 5);
}
#[test]
fn test_compute_instruction_size_minimal() {
let size =
X86CodeEmitterPrep::compute_instruction_size(X86Opcode::NOP, false, false, false, 0, 0);
assert_eq!(size, 1);
}
#[test]
fn test_add_fixup() {
let mut prep = X86CodeEmitterPrep::new();
let fixup = X86Fixup {
offset: 0,
target: "foo".to_string(),
kind: X86FixupKind::Rel32,
addend: 0,
};
prep.add_fixup(fixup);
assert_eq!(prep.num_fixups, 1);
}
#[test]
fn test_needs_relaxation() {
let prep = X86CodeEmitterPrep::new();
assert!(!prep.needs_relaxation(5, 10));
assert!(prep.needs_relaxation(15, 10));
}
#[test]
fn test_relax_instruction() {
let mut prep = X86CodeEmitterPrep::new();
prep.relax_instruction();
assert_eq!(prep.num_relaxed, 1);
}
#[test]
fn test_estimate_total_size() {
let mut prep = X86CodeEmitterPrep::new();
prep.estimate_total_size(100, 6);
assert_eq!(prep.estimated_code_size, 600);
}
#[test]
fn test_reserve_code_buffer() {
let mut prep = X86CodeEmitterPrep::new();
prep.estimate_total_size(100, 6);
let buf = prep.reserve_code_buffer();
assert!(buf >= 600);
assert!(buf <= 800); }
#[test]
fn test_fixup_kind_values() {
assert_eq!(X86FixupKind::Rel32, X86FixupKind::Rel32);
assert_ne!(X86FixupKind::Rel32, X86FixupKind::Abs64);
assert_ne!(X86FixupKind::GOTPCREL, X86FixupKind::PLT32);
}
#[test]
fn test_tls_model_values() {
assert_eq!(XLSTLSModel::GeneralDynamic, XLSTLSModel::GeneralDynamic);
assert_ne!(XLSTLSModel::GeneralDynamic, XLSTLSModel::LocalExec);
}
#[test]
fn test_tls_relax_no_change() {
let mut tls = X86TLSOptimizer::new();
tls.relax_model(XLSTLSModel::LocalExec, XLSTLSModel::LocalExec);
assert_eq!(tls.num_gd_to_ie, 0);
assert_eq!(tls.num_ie_to_le, 0);
assert_eq!(tls.num_ld_to_le, 0);
}
#[test]
fn test_constant_pool_empty_merge() {
let mut cp1 = X86ConstantPoolOptimizer::new();
let cp2 = X86ConstantPoolOptimizer::new();
cp1.merge_pools(&cp2);
assert_eq!(cp1.num_constants(), 0);
}
#[test]
fn test_emergency_spill_slot_default_unused() {
let slot = X86EmergencySpillSlot::new(0, 8, X86RegClass::GR32);
assert!(!slot.is_used);
}
#[test]
fn test_cond_br_total_optimizations() {
let mut cbf = X86CondBrFolding::new();
cbf.num_folded = 3;
cbf.num_reversed = 2;
cbf.num_removed = 1;
assert_eq!(cbf.total_optimizations(), 6);
}
#[test]
fn test_loop_alignment_cold_loop() {
let la = X86LoopAlignment::new(16);
assert!(!la.should_align(500, 1, false));
}
#[test]
fn test_loop_alignment_hot_large_loop() {
let la = X86LoopAlignment::new(16);
let align = X86LoopAlignment::compute_optimal_alignment(500, 10000, true);
assert_eq!(align, 8);
}
#[test]
fn test_verify_register_constraints_shift() {
let mut v = X86MachineVerifierCustom::new();
v.verify_register_constraints(X86Opcode::SHL, 2, false);
assert_eq!(v.num_warnings(), 1);
}
#[test]
fn test_verify_register_constraints_no_warn() {
let mut v = X86MachineVerifierCustom::new();
v.verify_register_constraints(X86Opcode::MOV, 0, true);
assert_eq!(v.num_warnings(), 0);
}
#[test]
fn test_fixup_kind_debug() {
assert!(format!("{:?}", X86FixupKind::PCRel32).contains("PCRel32"));
}
#[test]
fn test_code_emitter_various_sizes() {
let s1 =
X86CodeEmitterPrep::compute_instruction_size(X86Opcode::ADD, false, true, false, 0, 4);
let s2 =
X86CodeEmitterPrep::compute_instruction_size(X86Opcode::ADD, true, true, true, 4, 4);
assert!(s2 > s1);
}
}
#[derive(Debug, Clone)]
pub struct X86MCFixupKindInfo {
pub name: &'static str,
pub target_offset: u32,
pub target_size: u32,
pub flags: u32,
}
impl X86MCFixupKindInfo {
pub fn for_kind(kind: X86FixupKind) -> Self {
match kind {
X86FixupKind::Rel32 => Self {
name: "R_X86_64_32",
target_offset: 0,
target_size: 32,
flags: 0,
},
X86FixupKind::PCRel32 => Self {
name: "R_X86_64_PC32",
target_offset: 0,
target_size: 32,
flags: 1,
},
X86FixupKind::PCRel8 => Self {
name: "R_X86_64_PC8",
target_offset: 0,
target_size: 8,
flags: 1,
},
X86FixupKind::Abs32 => Self {
name: "R_X86_64_32S",
target_offset: 0,
target_size: 32,
flags: 0,
},
X86FixupKind::Abs64 => Self {
name: "R_X86_64_64",
target_offset: 0,
target_size: 64,
flags: 0,
},
X86FixupKind::GOTPCREL => Self {
name: "R_X86_64_GOTPCREL",
target_offset: 0,
target_size: 32,
flags: 1,
},
X86FixupKind::PLT32 => Self {
name: "R_X86_64_PLT32",
target_offset: 0,
target_size: 32,
flags: 1,
},
X86FixupKind::TlsGd => Self {
name: "R_X86_64_TLSGD",
target_offset: 0,
target_size: 32,
flags: 1,
},
X86FixupKind::TlsLd => Self {
name: "R_X86_64_TLSLD",
target_offset: 0,
target_size: 32,
flags: 1,
},
X86FixupKind::TlsIe => Self {
name: "R_X86_64_GOTTPOFF",
target_offset: 0,
target_size: 32,
flags: 1,
},
X86FixupKind::TlsLe => Self {
name: "R_X86_64_TPOFF32",
target_offset: 0,
target_size: 32,
flags: 0,
},
}
}
pub fn is_pc_relative(&self) -> bool {
self.flags & 1 != 0
}
pub fn size_in_bytes(&self) -> u32 {
(self.target_size + 7) / 8
}
}
#[derive(Debug, Clone)]
pub struct X86AsmBackendUtils {
pub is_64bit: bool,
pub cpu_string: String,
}
impl X86AsmBackendUtils {
pub fn new(is_64bit: bool, cpu: &str) -> Self {
Self {
is_64bit,
cpu_string: cpu.to_string(),
}
}
pub fn apply_fixup(code: &mut [u8], fixup: &X86Fixup, value: u64) -> bool {
let offset = fixup.offset as usize;
let info = X86MCFixupKindInfo::for_kind(fixup.kind);
let size = info.size_in_bytes() as usize;
if offset + size > code.len() {
return false;
}
let adjusted_value = if info.is_pc_relative() {
(value as i64).wrapping_sub(fixup.offset as i64 + fixup.addend) as u64
} else {
(value as i64).wrapping_add(fixup.addend) as u64
};
for i in 0..size {
code[offset + i] = ((adjusted_value >> (i * 8)) & 0xFF) as u8;
}
true
}
pub fn is_nop(bytes: &[u8]) -> bool {
if bytes.is_empty() {
return false;
}
if bytes[0] == 0x90 {
return true;
}
if bytes.len() >= 2 && bytes[0] == 0x66 && bytes[1] == 0x90 {
return true;
}
if bytes.len() >= 3 && bytes[0] == 0x0F && bytes[1] == 0x1F && bytes[2] == 0x00 {
return true;
}
false
}
pub fn is_terminal(opcode: X86Opcode) -> bool {
matches!(
opcode,
X86Opcode::RET | X86Opcode::JMP | X86Opcode::INT3 | X86Opcode::UD2
)
}
pub fn may_store(opcode: X86Opcode) -> bool {
matches!(
opcode,
X86Opcode::MOV
| X86Opcode::MOVSB
| X86Opcode::MOVSW
| X86Opcode::MOVSD_STR
| X86Opcode::MOVSQ
| X86Opcode::STOSB
| X86Opcode::STOSW
| X86Opcode::STOSD_STR
| X86Opcode::STOSQ
| X86Opcode::PUSH
| X86Opcode::CALL
)
}
pub fn may_load(opcode: X86Opcode) -> bool {
matches!(
opcode,
X86Opcode::MOV
| X86Opcode::MOVZX
| X86Opcode::MOVSX
| X86Opcode::LODSB
| X86Opcode::LODSW
| X86Opcode::LODSD_STR
| X86Opcode::LODSQ
| X86Opcode::POP
| X86Opcode::RET
)
}
}
#[derive(Debug, Clone)]
pub struct X86Bundle {
pub instrs: Vec<X86BundleInstr>,
pub bundle_size: u32,
}
#[derive(Debug, Clone)]
pub struct X86BundleInstr {
pub opcode: X86Opcode,
pub size: u32,
pub num_uops: u32,
pub ports: u32,
}
impl X86Bundle {
pub fn new(max_size: u32) -> Self {
Self {
instrs: Vec::new(),
bundle_size: max_size,
}
}
pub fn can_add(&self, instr: &X86BundleInstr) -> bool {
let current: u32 = self.instrs.iter().map(|i| i.size).sum();
current + instr.size <= self.bundle_size
}
pub fn add_instr(&mut self, instr: X86BundleInstr) -> bool {
if self.can_add(&instr) {
self.instrs.push(instr);
true
} else {
false
}
}
pub fn num_instrs(&self) -> usize {
self.instrs.len()
}
pub fn total_size(&self) -> u32 {
self.instrs.iter().map(|i| i.size).sum()
}
pub fn total_uops(&self) -> u32 {
self.instrs.iter().map(|i| i.num_uops).sum()
}
pub fn clear(&mut self) {
self.instrs.clear();
}
}
#[derive(Debug, Clone)]
pub struct X86AsmMatcherUtils {
pub features: X86TargetFeatures,
}
impl X86AsmMatcherUtils {
pub fn new(features: X86TargetFeatures) -> Self {
Self { features }
}
pub fn match_mnemonic(name: &str) -> Option<X86Opcode> {
match name.to_uppercase().as_str() {
"MOV" => Some(X86Opcode::MOV),
"ADD" => Some(X86Opcode::ADD),
"SUB" => Some(X86Opcode::SUB),
"MUL" => Some(X86Opcode::MUL),
"DIV" => Some(X86Opcode::DIV),
"AND" => Some(X86Opcode::AND),
"OR" => Some(X86Opcode::OR),
"XOR" => Some(X86Opcode::XOR),
"NOT" => Some(X86Opcode::NOT),
"NEG" => Some(X86Opcode::NEG),
"SHL" => Some(X86Opcode::SHL),
"SHR" => Some(X86Opcode::SHR),
"SAR" => Some(X86Opcode::SAR),
"CMP" => Some(X86Opcode::CMP),
"TEST" => Some(X86Opcode::TEST),
"LEA" => Some(X86Opcode::LEA),
"JMP" => Some(X86Opcode::JMP),
"CALL" => Some(X86Opcode::CALL),
"RET" => Some(X86Opcode::RET),
"NOP" => Some(X86Opcode::NOP),
"PUSH" => Some(X86Opcode::PUSH),
"POP" => Some(X86Opcode::POP),
"MOVZX" => Some(X86Opcode::MOVZX),
"MOVSX" => Some(X86Opcode::MOVSX),
"INC" => Some(X86Opcode::INC),
"DEC" => Some(X86Opcode::DEC),
"JE" => Some(X86Opcode::JE),
"JNE" => Some(X86Opcode::JNE),
"JL" => Some(X86Opcode::JL),
"JG" => Some(X86Opcode::JG),
"JLE" => Some(X86Opcode::JLE),
"JGE" => Some(X86Opcode::JGE),
"JB" => Some(X86Opcode::JB),
"JA" => Some(X86Opcode::JA),
"CMOVE" => Some(X86Opcode::CMOVE),
"CMOVNE" => Some(X86Opcode::CMOVNE),
"SETNE" => Some(X86Opcode::SETNE),
"SETE" => Some(X86Opcode::SETE),
_ => None,
}
}
pub fn is_mnemonic_valid(name: &str) -> bool {
Self::match_mnemonic(name).is_some()
}
pub fn feature_required(_opcode: X86Opcode) -> Option<&'static str> {
None }
}
#[derive(Debug, Clone)]
pub struct X86RegisterScavenger {
pub available_gprs: Vec<u32>,
pub available_xmms: Vec<u32>,
pub scavenged_count: usize,
}
impl X86RegisterScavenger {
pub fn new() -> Self {
Self {
available_gprs: Vec::new(),
available_xmms: Vec::new(),
scavenged_count: 0,
}
}
pub fn init_for_target(&mut self, is_64bit: bool) {
self.available_gprs.clear();
self.available_xmms.clear();
if is_64bit {
self.available_gprs.extend_from_slice(&[10, 11]);
self.available_xmms
.extend_from_slice(&[8, 9, 10, 11, 12, 13, 14, 15]);
} else {
self.available_gprs.extend_from_slice(&[0, 1, 2]);
self.available_xmms.extend_from_slice(&[4, 5, 6, 7]);
}
}
pub fn scavenge_gpr(&mut self) -> Option<u32> {
if let Some(reg) = self.available_gprs.pop() {
self.scavenged_count += 1;
Some(reg)
} else {
None
}
}
pub fn scavenge_xmm(&mut self) -> Option<u32> {
if let Some(reg) = self.available_xmms.pop() {
self.scavenged_count += 1;
Some(reg)
} else {
None
}
}
pub fn restore_register(&mut self, reg: u32, is_xmm: bool) {
if is_xmm {
self.available_xmms.push(reg);
} else {
self.available_gprs.push(reg);
}
}
pub fn is_exhausted(&self) -> bool {
self.available_gprs.is_empty() && self.available_xmms.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct X86InstrSizeDesc {
pub opcode_size: u8,
pub has_modrm: bool,
pub has_sib: bool,
pub displacement_size: u8,
pub immediate_size: u8,
pub prefix_size: u8,
pub rex_prefix: bool,
pub vex_prefix: bool,
pub evex_prefix: bool,
}
impl X86InstrSizeDesc {
pub fn new() -> Self {
Self {
opcode_size: 1,
has_modrm: false,
has_sib: false,
displacement_size: 0,
immediate_size: 0,
prefix_size: 0,
rex_prefix: false,
vex_prefix: false,
evex_prefix: false,
}
}
pub fn compute(
opcode: X86Opcode,
has_rex: bool,
has_modrm: bool,
has_sib: bool,
disp_size: u8,
imm_size: u8,
) -> Self {
Self {
opcode_size: if (opcode as u32) > 0xFF { 2 } else { 1 },
has_modrm,
has_sib,
displacement_size: disp_size,
immediate_size: imm_size,
prefix_size: 0,
rex_prefix: has_rex,
vex_prefix: false,
evex_prefix: false,
}
}
pub fn total_size(&self) -> u32 {
let mut total: u32 = self.opcode_size as u32;
if self.rex_prefix {
total += 1;
}
if self.vex_prefix {
total += 2;
}
if self.evex_prefix {
total += 3;
}
total += self.prefix_size as u32;
if self.has_modrm {
total += 1;
}
if self.has_sib {
total += 1;
}
total += self.displacement_size as u32;
total += self.immediate_size as u32;
total
}
}
#[derive(Debug, Clone)]
pub struct X86MCInstLower {
pub num_lowered: usize,
}
#[derive(Debug, Clone)]
pub struct MCInstLowered {
pub opcode: u32,
pub operands: Vec<MCOperandLowered>,
}
#[derive(Debug, Clone)]
pub enum MCOperandLowered {
Reg(u32),
Imm(i64),
Expr(String),
}
impl X86MCInstLower {
pub fn new() -> Self {
Self { num_lowered: 0 }
}
pub fn lower(&mut self, _instr: &MachineInstr) -> MCInstLowered {
self.num_lowered += 1;
MCInstLowered {
opcode: 0,
operands: Vec::new(),
}
}
pub fn lower_operand(op: &MachineOperand) -> MCOperandLowered {
match op {
MachineOperand::Reg(r) => MCOperandLowered::Reg(*r),
MachineOperand::Imm(v) => MCOperandLowered::Imm(*v),
MachineOperand::Global(_) => MCOperandLowered::Expr("mem".to_string()),
_ => MCOperandLowered::Expr("unknown".to_string()),
}
}
pub fn get_mc_opcode(x86_opcode: X86Opcode) -> u32 {
x86_opcode as u32
}
}
#[cfg(test)]
mod final_comprehensive_tests {
use super::*;
#[test]
fn test_mc_fixup_info_rel32() {
let info = X86MCFixupKindInfo::for_kind(X86FixupKind::Rel32);
assert!(!info.is_pc_relative());
assert_eq!(info.size_in_bytes(), 4);
}
#[test]
fn test_mc_fixup_info_pcrel32() {
let info = X86MCFixupKindInfo::for_kind(X86FixupKind::PCRel32);
assert!(info.is_pc_relative());
assert_eq!(info.size_in_bytes(), 4);
}
#[test]
fn test_mc_fixup_info_all_kinds() {
let kinds = [
X86FixupKind::Rel32,
X86FixupKind::PCRel32,
X86FixupKind::PCRel8,
X86FixupKind::Abs32,
X86FixupKind::Abs64,
X86FixupKind::GOTPCREL,
X86FixupKind::PLT32,
X86FixupKind::TlsGd,
X86FixupKind::TlsLd,
X86FixupKind::TlsIe,
X86FixupKind::TlsLe,
];
for kind in &kinds {
let info = X86MCFixupKindInfo::for_kind(*kind);
assert!(info.size_in_bytes() > 0);
assert!(!info.name.is_empty());
}
}
#[test]
fn test_asm_backend_utils_nop_detection() {
assert!(X86AsmBackendUtils::is_nop(&[0x90]));
assert!(X86AsmBackendUtils::is_nop(&[0x66, 0x90]));
assert!(X86AsmBackendUtils::is_nop(&[0x0F, 0x1F, 0x00]));
assert!(!X86AsmBackendUtils::is_nop(&[]));
assert!(!X86AsmBackendUtils::is_nop(&[0xCC]));
}
#[test]
fn test_is_terminal() {
assert!(X86AsmBackendUtils::is_terminal(X86Opcode::RET));
assert!(X86AsmBackendUtils::is_terminal(X86Opcode::JMP));
assert!(!X86AsmBackendUtils::is_terminal(X86Opcode::MOV));
}
#[test]
fn test_may_store() {
assert!(X86AsmBackendUtils::may_store(X86Opcode::MOV));
assert!(X86AsmBackendUtils::may_store(X86Opcode::PUSH));
assert!(!X86AsmBackendUtils::may_store(X86Opcode::ADD));
}
#[test]
fn test_may_load() {
assert!(X86AsmBackendUtils::may_load(X86Opcode::MOV));
assert!(X86AsmBackendUtils::may_load(X86Opcode::POP));
assert!(!X86AsmBackendUtils::may_load(X86Opcode::ADD));
}
#[test]
fn test_bundle_creation() {
let b = X86Bundle::new(16);
assert_eq!(b.num_instrs(), 0);
assert_eq!(b.total_size(), 0);
}
#[test]
fn test_bundle_add_instr() {
let mut b = X86Bundle::new(16);
let instr = X86BundleInstr {
opcode: X86Opcode::ADD,
size: 3,
num_uops: 1,
ports: 0x0F,
};
assert!(b.add_instr(instr));
assert_eq!(b.num_instrs(), 1);
}
#[test]
fn test_bundle_cannot_exceed() {
let mut b = X86Bundle::new(4);
let instr = X86BundleInstr {
opcode: X86Opcode::ADD,
size: 3,
num_uops: 1,
ports: 0x0F,
};
assert!(b.add_instr(instr.clone()));
assert!(!b.add_instr(instr)); }
#[test]
fn test_bundle_total_uops() {
let mut b = X86Bundle::new(32);
b.add_instr(X86BundleInstr {
opcode: X86Opcode::ADD,
size: 3,
num_uops: 1,
ports: 0,
});
b.add_instr(X86BundleInstr {
opcode: X86Opcode::MUL,
size: 3,
num_uops: 3,
ports: 0,
});
assert_eq!(b.total_uops(), 4);
}
#[test]
fn test_bundle_clear() {
let mut b = X86Bundle::new(32);
b.add_instr(X86BundleInstr {
opcode: X86Opcode::ADD,
size: 3,
num_uops: 1,
ports: 0,
});
b.clear();
assert_eq!(b.num_instrs(), 0);
}
#[test]
fn test_asm_matcher_valid_mnemonics() {
assert!(X86AsmMatcherUtils::is_mnemonic_valid("MOV"));
assert!(X86AsmMatcherUtils::is_mnemonic_valid("add"));
assert!(X86AsmMatcherUtils::is_mnemonic_valid("XOR"));
assert!(!X86AsmMatcherUtils::is_mnemonic_valid("INVALID"));
}
#[test]
fn test_asm_matcher_match_specific() {
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("MOV"),
Some(X86Opcode::MOV)
);
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("LEA"),
Some(X86Opcode::LEA)
);
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("NOP"),
Some(X86Opcode::NOP)
);
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("JE"),
Some(X86Opcode::JE)
);
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("CMOVNE"),
Some(X86Opcode::CMOVNE)
);
}
#[test]
fn test_register_scavenger_creation() {
let mut rs = X86RegisterScavenger::new();
assert!(!rs.is_exhausted()); }
#[test]
fn test_register_scavenger_init_64() {
let mut rs = X86RegisterScavenger::new();
rs.init_for_target(true);
assert!(!rs.is_exhausted());
assert!(rs.scavenge_gpr().is_some());
}
#[test]
fn test_register_scavenger_init_32() {
let mut rs = X86RegisterScavenger::new();
rs.init_for_target(false);
assert!(rs.scavenge_gpr().is_some());
}
#[test]
fn test_register_scavenger_exhaust() {
let mut rs = X86RegisterScavenger::new();
rs.init_for_target(true);
while rs.scavenge_gpr().is_some() {}
while rs.scavenge_xmm().is_some() {}
assert!(rs.is_exhausted());
}
#[test]
fn test_register_scavenger_restore() {
let mut rs = X86RegisterScavenger::new();
rs.init_for_target(true);
let reg = rs.scavenge_gpr().unwrap();
rs.restore_register(reg, false);
assert!(rs.available_gprs.contains(®));
}
#[test]
fn test_instr_size_desc_creation() {
let desc = X86InstrSizeDesc::new();
assert_eq!(desc.total_size(), 1);
}
#[test]
fn test_instr_size_desc_complex() {
let desc = X86InstrSizeDesc::compute(X86Opcode::ADD, true, true, true, 4, 4);
assert!(desc.total_size() > 8);
}
#[test]
fn test_instr_size_desc_minimal() {
let desc = X86InstrSizeDesc::compute(X86Opcode::NOP, false, false, false, 0, 0);
assert_eq!(desc.total_size(), 1);
}
#[test]
fn test_mc_inst_lower_creation() {
let lower = X86MCInstLower::new();
assert_eq!(lower.num_lowered, 0);
}
#[test]
fn test_lower_operand_reg() {
let op = MachineOperand::Reg(0);
let lowered = X86MCInstLower::lower_operand(&op);
assert!(matches!(lowered, MCOperandLowered::Reg(0)));
}
#[test]
fn test_lower_operand_imm() {
let op = MachineOperand::Imm(42);
let lowered = X86MCInstLower::lower_operand(&op);
assert!(matches!(lowered, MCOperandLowered::Imm(42)));
}
#[test]
fn test_get_mc_opcode() {
assert_eq!(X86MCInstLower::get_mc_opcode(X86Opcode::ADD), 2);
assert_eq!(X86MCInstLower::get_mc_opcode(X86Opcode::MOV), 1);
}
#[test]
fn test_apply_fixup_simple() {
let utils = X86AsmBackendUtils::new(true, "generic");
let fixup = X86Fixup {
offset: 0,
target: "".to_string(),
kind: X86FixupKind::Abs32,
addend: 0,
};
let mut code = vec![0u8; 4];
assert!(X86AsmBackendUtils::apply_fixup(
&mut code, &fixup, 0x12345678
));
assert_eq!(code[0], 0x78);
assert_eq!(code[1], 0x56);
assert_eq!(code[2], 0x34);
assert_eq!(code[3], 0x12);
}
#[test]
fn test_apply_fixup_out_of_bounds() {
let fixup = X86Fixup {
offset: 10,
target: "".to_string(),
kind: X86FixupKind::Abs32,
addend: 0,
};
let mut code = vec![0u8; 4];
assert!(!X86AsmBackendUtils::apply_fixup(&mut code, &fixup, 0));
}
#[test]
fn test_apply_fixup_pcrel() {
let fixup = X86Fixup {
offset: 0,
target: "".to_string(),
kind: X86FixupKind::PCRel32,
addend: -4,
};
let mut code = vec![0u8; 4];
assert!(X86AsmBackendUtils::apply_fixup(&mut code, &fixup, 100));
assert_eq!(code[0], 96u8);
}
#[test]
fn test_fixup_kind_clone_copy() {
let k1 = X86FixupKind::Rel32;
let k2 = k1;
assert_eq!(k1, k2);
}
#[test]
fn test_bundle_instr_clone() {
let bi = X86BundleInstr {
opcode: X86Opcode::ADD,
size: 3,
num_uops: 1,
ports: 0,
};
let bi2 = bi.clone();
assert_eq!(bi2.opcode, X86Opcode::ADD);
assert_eq!(bi2.size, 3);
}
#[test]
fn test_mc_inst_lowered_clone() {
let mi = MCInstLowered {
opcode: 1,
operands: vec![MCOperandLowered::Reg(0)],
};
let mi2 = mi.clone();
assert_eq!(mi2.opcode, 1);
}
#[test]
fn test_instr_size_desc_clone() {
let desc = X86InstrSizeDesc::compute(X86Opcode::ADD, true, true, false, 4, 0);
let desc2 = desc.clone();
assert_eq!(desc.total_size(), desc2.total_size());
}
#[test]
fn test_asm_matcher_utils_clone() {
let am = X86AsmMatcherUtils::new(X86TargetFeatures::default());
let am2 = am.clone();
assert_eq!(am2.features.is_64bit, true);
}
#[test]
fn test_register_scavenger_clone() {
let mut rs = X86RegisterScavenger::new();
rs.init_for_target(true);
let rs2 = rs.clone();
assert_eq!(rs2.available_gprs.len(), rs.available_gprs.len());
}
#[test]
fn test_bundle_can_add_edge() {
let b = X86Bundle::new(3);
let instr = X86BundleInstr {
opcode: X86Opcode::NOP,
size: 3,
num_uops: 1,
ports: 0,
};
assert!(b.can_add(&instr));
let b2 = X86Bundle::new(2);
assert!(!b2.can_add(&instr));
}
}
#[derive(Debug, Clone)]
pub struct X86RegisterInfoUtils {
pub is_64bit: bool,
pub num_gprs: u32,
pub num_xmms: u32,
pub reserved_regs: HashSet<u32>,
}
impl X86RegisterInfoUtils {
pub fn new(is_64bit: bool) -> Self {
let mut reserved = HashSet::new();
reserved.insert(4); if is_64bit {
Self {
is_64bit,
num_gprs: 16,
num_xmms: 16,
reserved_regs: reserved,
}
} else {
Self {
is_64bit,
num_gprs: 8,
num_xmms: 8,
reserved_regs: reserved,
}
}
}
pub fn is_reserved(&self, reg: u32) -> bool {
self.reserved_regs.contains(®)
}
pub fn is_grp(&self, reg: u32) -> bool {
reg < self.num_gprs
}
pub fn is_xmm(&self, reg: u32) -> bool {
reg >= 32 && reg < 32 + self.num_xmms
}
pub fn is_allocatable(&self, reg: u32) -> bool {
!self.is_reserved(reg) && (self.is_grp(reg) || self.is_xmm(reg))
}
pub fn reserve_reg(&mut self, reg: u32) {
self.reserved_regs.insert(reg);
}
pub fn get_stack_pointer(&self) -> u32 {
4
}
pub fn get_frame_pointer(&self) -> Option<u32> {
Some(5)
}
pub fn get_return_address_reg(&self) -> u32 {
if self.is_64bit {
0
} else {
0
}
} pub fn get_callee_saved_regs(&self) -> Vec<u32> {
if self.is_64bit {
vec![3, 5, 6, 7, 12, 13, 14, 15]
} else {
vec![3, 5, 6, 7]
}
}
pub fn get_caller_saved_regs(&self) -> Vec<u32> {
if self.is_64bit {
vec![0, 1, 2, 8, 9, 10, 11]
} else {
vec![0, 1, 2]
}
}
}
#[derive(Debug, Clone)]
pub struct X86CallFrameInfo {
pub call_frame_size: u64,
pub stack_alignment: u32,
pub has_dynamic_alloca: bool,
pub max_call_args: u32,
pub uses_red_zone: bool,
}
impl X86CallFrameInfo {
pub fn new(is_64bit: bool, abi: X86ABIType) -> Self {
Self {
call_frame_size: 0,
stack_alignment: 16,
has_dynamic_alloca: false,
max_call_args: 0,
uses_red_zone: is_64bit && matches!(abi, X86ABIType::SystemV),
}
}
pub fn estimate_call_frame_size(&mut self, num_args: u32, has_varargs: bool) {
let base = num_args as u64 * 8;
self.call_frame_size = if has_varargs { base + 64 } else { base };
self.max_call_args = self.max_call_args.max(num_args);
}
pub fn align_call_frame(&mut self) {
self.call_frame_size = (self.call_frame_size + self.stack_alignment as u64 - 1)
& !(self.stack_alignment as u64 - 1);
}
}
#[derive(Debug, Clone)]
pub struct X86VEXPrefixBuilder {
pub vex_type: X86VEXType,
pub r_bit: bool,
pub x_bit: bool,
pub b_bit: bool,
pub m_mmmm: u8,
pub w_bit: bool,
pub vvvv: u8,
pub l_bit: bool,
pub pp: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86VEXType {
VEX2, VEX3, EVEX, }
impl X86VEXPrefixBuilder {
pub fn new() -> Self {
Self {
vex_type: X86VEXType::VEX2,
r_bit: false,
x_bit: false,
b_bit: false,
m_mmmm: 1,
w_bit: false,
vvvv: 0,
l_bit: false,
pp: 0,
}
}
pub fn encode(&self) -> Vec<u8> {
match self.vex_type {
X86VEXType::VEX2 => {
let byte1: u8 = 0xC5;
let byte2: u8 = ((!self.r_bit as u8) << 7)
| ((self.vvvv & 0x0F) << 3)
| ((self.l_bit as u8) << 2)
| (self.pp & 0x03);
vec![byte1, byte2]
}
X86VEXType::VEX3 => {
let byte1: u8 = 0xC4;
let byte2: u8 = ((!self.r_bit as u8) << 7)
| ((!self.x_bit as u8) << 6)
| ((!self.b_bit as u8) << 5)
| (self.m_mmmm & 0x1F);
let byte3: u8 = ((self.w_bit as u8) << 7)
| ((self.vvvv & 0x0F) << 3)
| ((self.l_bit as u8) << 2)
| (self.pp & 0x03);
vec![byte1, byte2, byte3]
}
X86VEXType::EVEX => {
let byte1: u8 = 0x62;
let byte2: u8 = ((!self.r_bit as u8) << 7)
| ((!self.x_bit as u8) << 6)
| ((!self.b_bit as u8) << 5)
| 0x04; let byte3: u8 =
((self.m_mmmm & 0x07) << 5) | ((!self.w_bit as u8) << 4) | (self.vvvv & 0x0F);
let byte4: u8 = ((self.l_bit as u8) << 5) | (self.pp & 0x03);
vec![byte1, byte2, byte3, byte4]
}
}
}
pub fn set_256bit(&mut self) {
self.l_bit = true;
self.vex_type = if self.vex_type == X86VEXType::VEX2 {
X86VEXType::VEX3
} else {
self.vex_type
};
}
pub fn set_512bit(&mut self) {
self.vex_type = X86VEXType::EVEX;
}
pub fn prefix_size(&self) -> u32 {
match self.vex_type {
X86VEXType::VEX2 => 2,
X86VEXType::VEX3 => 3,
X86VEXType::EVEX => 4,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct X86REXPrefixBuilder {
pub w: bool,
pub r: bool,
pub x: bool,
pub b: bool,
}
impl X86REXPrefixBuilder {
pub fn new() -> Self {
Self {
w: false,
r: false,
x: false,
b: false,
}
}
pub fn encode(&self) -> u8 {
let mut rex: u8 = 0x40;
if self.w {
rex |= 0x08;
}
if self.r {
rex |= 0x04;
}
if self.x {
rex |= 0x02;
}
if self.b {
rex |= 0x01;
}
rex
}
pub fn is_needed(&self) -> bool {
self.w || self.r || self.x || self.b
}
pub fn set_w(&mut self) {
self.w = true;
}
pub fn set_for_64bit(&mut self) {
self.w = true;
}
pub fn set_extension_bits(&mut self, reg: u32) {
if reg >= 8 {
self.r = true;
}
}
}
#[cfg(test)]
mod final_test_block {
use super::*;
#[test]
fn test_register_info_utils_64() {
let info = X86RegisterInfoUtils::new(true);
assert!(info.is_64bit);
assert_eq!(info.num_gprs, 16);
assert!(info.is_reserved(4)); assert!(info.is_grp(0));
assert!(!info.is_grp(32));
assert!(info.is_xmm(32));
}
#[test]
fn test_register_info_utils_32() {
let info = X86RegisterInfoUtils::new(false);
assert!(!info.is_64bit);
assert_eq!(info.num_gprs, 8);
}
#[test]
fn test_register_info_allocatable() {
let info = X86RegisterInfoUtils::new(true);
assert!(info.is_allocatable(0)); assert!(!info.is_allocatable(4)); }
#[test]
fn test_register_info_reserve() {
let mut info = X86RegisterInfoUtils::new(true);
info.reserve_reg(0);
assert!(info.is_reserved(0));
assert!(!info.is_allocatable(0));
}
#[test]
fn test_register_info_stack_pointer() {
for &is64 in &[true, false] {
let info = X86RegisterInfoUtils::new(is64);
assert_eq!(info.get_stack_pointer(), 4);
}
}
#[test]
fn test_register_info_frame_pointer() {
let info = X86RegisterInfoUtils::new(true);
assert_eq!(info.get_frame_pointer(), Some(5));
}
#[test]
fn test_register_info_return_address() {
let info = X86RegisterInfoUtils::new(true);
assert_eq!(info.get_return_address_reg(), 0);
}
#[test]
fn test_callee_saved_64() {
let info = X86RegisterInfoUtils::new(true);
let saved = info.get_callee_saved_regs();
assert!(saved.contains(&3));
assert!(saved.contains(&12));
}
#[test]
fn test_caller_saved_64() {
let info = X86RegisterInfoUtils::new(true);
let saved = info.get_caller_saved_regs();
assert!(saved.contains(&0));
assert!(saved.contains(&10));
}
#[test]
fn test_call_frame_info_creation() {
let cfi = X86CallFrameInfo::new(true, X86ABIType::SystemV);
assert!(cfi.uses_red_zone);
assert_eq!(cfi.call_frame_size, 0);
}
#[test]
fn test_call_frame_info_no_red_zone() {
let cfi = X86CallFrameInfo::new(true, X86ABIType::Microsoft);
assert!(!cfi.uses_red_zone);
}
#[test]
fn test_estimate_call_frame_size() {
let mut cfi = X86CallFrameInfo::new(true, X86ABIType::SystemV);
cfi.estimate_call_frame_size(4, false);
assert_eq!(cfi.call_frame_size, 32);
cfi.estimate_call_frame_size(6, false);
assert_eq!(cfi.max_call_args, 6);
}
#[test]
fn test_estimate_call_frame_size_varargs() {
let mut cfi = X86CallFrameInfo::new(true, X86ABIType::SystemV);
cfi.estimate_call_frame_size(2, true);
assert_eq!(cfi.call_frame_size, 80);
}
#[test]
fn test_align_call_frame() {
let mut cfi = X86CallFrameInfo::new(true, X86ABIType::SystemV);
cfi.call_frame_size = 33;
cfi.align_call_frame();
assert_eq!(cfi.call_frame_size, 48);
}
#[test]
fn test_vex_prefix_builder_new() {
let v = X86VEXPrefixBuilder::new();
assert_eq!(v.prefix_size(), 2);
let bytes = v.encode();
assert_eq!(bytes.len(), 2);
assert_eq!(bytes[0], 0xC5);
}
#[test]
fn test_vex_prefix_256bit() {
let mut v = X86VEXPrefixBuilder::new();
v.set_256bit();
assert_eq!(v.prefix_size(), 3);
}
#[test]
fn test_vex_prefix_512bit() {
let mut v = X86VEXPrefixBuilder::new();
v.set_512bit();
assert_eq!(v.prefix_size(), 4);
}
#[test]
fn test_vex_encode_vex3() {
let v = X86VEXPrefixBuilder {
vex_type: X86VEXType::VEX3,
r_bit: true,
x_bit: false,
b_bit: true,
m_mmmm: 2,
w_bit: true,
vvvv: 3,
l_bit: true,
pp: 1,
};
let bytes = v.encode();
assert_eq!(bytes.len(), 3);
assert_eq!(bytes[0], 0xC4);
}
#[test]
fn test_vex_encode_evex() {
let v = X86VEXPrefixBuilder {
vex_type: X86VEXType::EVEX,
r_bit: false,
x_bit: false,
b_bit: false,
m_mmmm: 1,
w_bit: true,
vvvv: 0,
l_bit: true,
pp: 0,
};
let bytes = v.encode();
assert_eq!(bytes.len(), 4);
assert_eq!(bytes[0], 0x62);
}
#[test]
fn test_rex_prefix_new() {
let rex = X86REXPrefixBuilder::new();
assert!(!rex.is_needed());
assert_eq!(rex.encode(), 0x40);
}
#[test]
fn test_rex_prefix_w() {
let mut rex = X86REXPrefixBuilder::new();
rex.set_w();
assert!(rex.is_needed());
assert_eq!(rex.encode(), 0x48);
}
#[test]
fn test_rex_prefix_64bit() {
let mut rex = X86REXPrefixBuilder::new();
rex.set_for_64bit();
assert_eq!(rex.encode(), 0x48);
}
#[test]
fn test_rex_prefix_extension_bits() {
let mut rex = X86REXPrefixBuilder::new();
rex.set_extension_bits(8);
assert!(rex.r);
assert!(rex.is_needed());
}
#[test]
fn test_rex_prefix_full() {
let rex = X86REXPrefixBuilder {
w: true,
r: true,
x: true,
b: true,
};
assert_eq!(rex.encode(), 0x4F);
}
#[test]
fn test_all_components_integration() {
let features = X86TargetFeatures::default();
let mut transform = X86CodeGenTransform::new();
let mut dag = SelectionDAG::with_target("x86_64-unknown-linux-gnu");
let x = dag.get_constant(42, Type::i32());
let y = dag.get_constant(10, Type::i32());
let _sum = dag.get_binary_op(SDOpcode::Add, Type::i32(), x, y);
transform.run_on_dag(&mut dag);
assert!(dag.num_nodes() > 1);
}
#[test]
fn test_transform_with_features() {
for features in &[
X86TargetFeatures::default(),
X86TargetFeatures {
has_avx512: true,
has_fma: true,
has_popcnt: true,
has_lzcnt: true,
},
X86TargetFeatures { is_64bit: false },
] {
let t = X86CodeGenTransform::with_features(*features);
assert_eq!(t.features.is_64bit, features.is_64bit);
}
}
#[test]
fn test_full_codegen_pipeline() {
let features = X86TargetFeatures::default();
let mut dag = SelectionDAG::with_target("x86_64");
let a = dag.get_constant(100, Type::i32());
let b = dag.get_constant(200, Type::i32());
dag.get_binary_op(SDOpcode::Add, Type::i32(), a, b);
dag.get_binary_op(SDOpcode::Mul, Type::i32(), a, b);
dag.get_binary_op(SDOpcode::And, Type::i32(), a, b);
let mut transform = X86CodeGenTransform::with_features(features);
transform.run_on_dag(&mut dag);
assert!(transform.stats.dag_combines >= 0);
}
#[test]
fn test_type_and_op_legalization_pipeline() {
let features = X86TargetFeatures::default();
let mut type_legalizer = X86TypeLegalizer::new(features);
let mut op_legalizer = X86OperationLegalizer::new(features);
let i128 = Type::int(128);
let ops = vec![i128.clone(), i128.clone()];
let action = type_legalizer.get_legalization_action(&i128);
assert_eq!(action, X86LegalizeAction::SplitIntoTwoI64);
let op_action = op_legalizer.get_operation_action(SDOpcode::Add, &i128, &ops);
assert_eq!(op_action, X86OpLegalizeAction::Expand);
let result = op_legalizer.legalize_operation(SDOpcode::Add, &i128, &ops);
assert!(matches!(result, X86OpLegalizeResult::Expanded(_)));
}
#[test]
fn test_codegen_prepare_with_type_legalization() {
let features = X86TargetFeatures::default();
let mut dag = SelectionDAG::with_target("x86_64");
let val = dag.get_constant(0xDEADBEEF, Type::int(128));
let _ = dag.get_constant(0xCAFE, Type::int(128));
dag.get_binary_op(SDOpcode::Add, Type::int(128), val, val);
let mut prep = X86CodeGenPrepare::new();
prep.run_on_dag(&mut dag, &features);
assert!(prep.stats.int_splits >= 0);
}
#[test]
fn test_machine_instruction_combining_pipeline() {
let mut bb = MachineBasicBlock::default();
bb.instructions.push(MachineInstr {
opcode: 1, operands: vec![
MachineOperand::Reg(0),
MachineOperand::Global("mem".to_string()),
],
..Default::default()
});
bb.instructions.push(MachineInstr {
opcode: 2, operands: vec![
MachineOperand::Reg(0),
MachineOperand::Reg(0),
MachineOperand::Reg(1),
],
..Default::default()
});
let mut combiner = X86InstrCombiner::new();
let count = combiner.combine_block(&mut bb);
assert!(count >= 0);
}
#[test]
fn test_dominator_on_diamond() {
let mut sw = X86ShrinkWrapPass::new();
let blocks = vec![0, 1, 2, 3];
let edges = vec![(0, 1), (0, 2), (1, 3), (2, 3)];
sw.compute_dominator_sets(&blocks, &edges, 0);
assert!(sw.is_dominated_by(1, 0));
assert!(sw.is_dominated_by(2, 0));
assert!(sw.is_dominated_by(3, 0));
assert!(!sw.is_dominated_by(1, 2)); assert!(!sw.is_dominated_by(2, 1)); }
#[test]
fn test_nop_sequences_all_sizes() {
for size in 0..=20 {
let nops = X86PadShortFunctions::generate_nops(size);
assert_eq!(
nops.len(),
size as usize,
"NOP sequence length mismatch for size {}",
size
);
}
}
#[test]
fn test_const_pool_different_types() {
let mut cp = X86ConstantPoolOptimizer::new();
let off1 = cp.add_constant(42, 1); let off2 = cp.add_constant(42, 2); assert_ne!(off1, off2); assert_eq!(cp.num_constants(), 2);
}
#[test]
fn test_codegen_transform_debug_mode() {
let mut t = X86CodeGenTransform::new();
assert!(!t.debug);
t.debug = true;
assert!(t.debug);
}
#[test]
fn test_codegen_transform_disabled() {
let mut t = X86CodeGenTransform::new();
t.enabled = false;
let mut dag = SelectionDAG::with_target("x86_64");
let x = dag.get_constant(42, Type::i32());
dag.get_constant(10, Type::i32());
dag.get_binary_op(SDOpcode::Add, Type::i32(), x, x);
let count_before = dag.num_nodes();
t.run_on_dag(&mut dag);
assert_eq!(dag.num_nodes(), count_before);
}
#[test]
fn test_rex_prefix_copy() {
let rex = X86REXPrefixBuilder {
w: true,
r: false,
x: true,
b: false,
};
let rex2 = rex;
assert_eq!(rex.encode(), rex2.encode());
}
#[test]
fn test_vex_type_equality() {
assert_eq!(X86VEXType::VEX2, X86VEXType::VEX2);
assert_ne!(X86VEXType::VEX2, X86VEXType::EVEX);
}
#[test]
fn test_mc_operand_lowered_variants() {
let r = MCOperandLowered::Reg(0);
let i = MCOperandLowered::Imm(42);
let e = MCOperandLowered::Expr("test".to_string());
assert!(matches!(r, MCOperandLowered::Reg(0)));
assert!(matches!(i, MCOperandLowered::Imm(42)));
assert!(matches!(e, MCOperandLowered::Expr(_)));
}
#[test]
fn test_mc_fixup_info_pcrel8() {
let info = X86MCFixupKindInfo::for_kind(X86FixupKind::PCRel8);
assert!(info.is_pc_relative());
assert_eq!(info.size_in_bytes(), 1);
}
#[test]
fn test_asm_matcher_case_insensitive() {
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("mov"),
Some(X86Opcode::MOV)
);
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("Add"),
Some(X86Opcode::ADD)
);
assert_eq!(
X86AsmMatcherUtils::match_mnemonic("xor"),
Some(X86Opcode::XOR)
);
}
#[test]
fn test_codegen_transform_stats_accumulate() {
let mut t = X86CodeGenTransform::new();
t.stats.dag_combines += 1;
t.stats.type_legalizations += 2;
t.stats.op_legalizations += 3;
assert_eq!(t.stats.dag_combines, 1);
assert_eq!(t.stats.type_legalizations, 2);
assert_eq!(t.stats.op_legalizations, 3);
}
}
#[derive(Debug, Clone)]
pub struct X86PatchPointInserter {
pub num_patchpoints: usize,
pub num_stackmaps: usize,
pub next_id: u64,
}
impl X86PatchPointInserter {
pub fn new() -> Self {
Self {
num_patchpoints: 0,
num_stackmaps: 0,
next_id: 0,
}
}
pub fn create_patchpoint(&mut self, _num_args: u32, _code_size: u32) -> u64 {
let id = self.next_id;
self.next_id += 1;
self.num_patchpoints += 1;
id
}
pub fn create_stackmap(&mut self, _num_locations: u32) -> u64 {
let id = self.next_id;
self.next_id += 1;
self.num_stackmaps += 1;
id
}
pub fn patchpoint_size(_num_args: u32, _num_nop_bytes: u32) -> u32 {
12 + _num_nop_bytes }
pub fn stackmap_size(_num_locations: u32) -> u32 {
8 + _num_locations * 4 }
}
#[derive(Debug, Clone)]
pub struct X86EHLabelUtils {
pub label_counter: u64,
pub eh_labels: HashMap<String, u64>,
}
impl X86EHLabelUtils {
pub fn new() -> Self {
Self {
label_counter: 0,
eh_labels: HashMap::new(),
}
}
pub fn create_eh_label(&mut self, prefix: &str) -> String {
let id = self.label_counter;
self.label_counter += 1;
let label = format!(".L{}_{}", prefix, id);
self.eh_labels.insert(label.clone(), id);
label
}
pub fn create_lpad_label(&mut self, func_name: &str, idx: u32) -> String {
let label = format!(".L{}_lpad{}", func_name, idx);
self.eh_labels.insert(label.clone(), self.label_counter);
self.label_counter += 1;
label
}
pub fn create_lsda_label(&mut self, func_name: &str) -> String {
let label = format!(".L{}_lsda", func_name);
self.eh_labels.insert(label.clone(), self.label_counter);
self.label_counter += 1;
label
}
pub fn get_label_id(&self, label: &str) -> Option<u64> {
self.eh_labels.get(label).copied()
}
pub fn num_labels(&self) -> usize {
self.eh_labels.len()
}
}
#[derive(Debug, Clone)]
pub struct X86JumpTableManager {
pub tables: Vec<X86JumpTable>,
pub next_id: u64,
}
#[derive(Debug, Clone)]
pub struct X86JumpTable {
pub id: u64,
pub label: String,
pub min_value: u64,
pub max_value: u64,
pub entries: Vec<String>,
pub default_target: String,
}
impl X86JumpTableManager {
pub fn new() -> Self {
Self {
tables: Vec::new(),
next_id: 0,
}
}
pub fn create_table(&mut self, min: u64, max: u64, default: &str) -> &X86JumpTable {
let id = self.next_id;
self.next_id += 1;
let label = format!(".LJTI{}", id);
let table = X86JumpTable {
id,
label,
min_value: min,
max_value: max,
entries: Vec::new(),
default_target: default.to_string(),
};
self.tables.push(table);
self.tables.last().unwrap()
}
pub fn add_entry(&mut self, table_id: u64, target: &str) {
if let Some(table) = self.tables.iter_mut().find(|t| t.id == table_id) {
table.entries.push(target.to_string());
}
}
pub fn compute_entry_offset(index: u64) -> u64 {
index * 4 }
pub fn entry_count(&self, table_id: u64) -> usize {
self.tables
.iter()
.find(|t| t.id == table_id)
.map(|t| t.entries.len())
.unwrap_or(0)
}
pub fn total_size(&self) -> u64 {
self.tables
.iter()
.map(|t| (t.max_value - t.min_value + 1) * 4)
.sum()
}
pub fn num_tables(&self) -> usize {
self.tables.len()
}
}
#[cfg(test)]
mod rapid_fire_tests {
use super::*;
#[test]
fn test_patchpoint_inserter_new() {
let pp = X86PatchPointInserter::new();
assert_eq!(pp.num_patchpoints, 0);
assert_eq!(pp.next_id, 0);
}
#[test]
fn test_create_patchpoint() {
let mut pp = X86PatchPointInserter::new();
let id = pp.create_patchpoint(3, 16);
assert_eq!(id, 0);
assert_eq!(pp.num_patchpoints, 1);
let id2 = pp.create_patchpoint(5, 32);
assert_eq!(id2, 1);
}
#[test]
fn test_create_stackmap() {
let mut pp = X86PatchPointInserter::new();
let id = pp.create_stackmap(4);
assert_eq!(id, 0);
assert_eq!(pp.num_stackmaps, 1);
}
#[test]
fn test_patchpoint_sizes() {
assert!(X86PatchPointInserter::patchpoint_size(3, 16) > 12);
assert!(X86PatchPointInserter::stackmap_size(4) > 8);
}
#[test]
fn test_eh_label_utils_new() {
let eh = X86EHLabelUtils::new();
assert_eq!(eh.num_labels(), 0);
}
#[test]
fn test_create_eh_label() {
let mut eh = X86EHLabelUtils::new();
let l = eh.create_eh_label("test");
assert!(l.starts_with(".Ltest_"));
assert_eq!(eh.num_labels(), 1);
}
#[test]
fn test_create_lpad_label() {
let mut eh = X86EHLabelUtils::new();
let l = eh.create_lpad_label("foo", 3);
assert!(l.contains("foo"));
assert!(l.contains("lpad"));
}
#[test]
fn test_create_lsda_label() {
let mut eh = X86EHLabelUtils::new();
let l = eh.create_lsda_label("foo");
assert!(l.contains("foo"));
assert!(l.contains("lsda"));
}
#[test]
fn test_get_label_id() {
let mut eh = X86EHLabelUtils::new();
let l = eh.create_eh_label("get");
assert!(eh.get_label_id(&l).is_some());
assert!(eh.get_label_id("nonexistent").is_none());
}
#[test]
fn test_jump_table_manager_new() {
let jt = X86JumpTableManager::new();
assert_eq!(jt.num_tables(), 0);
assert_eq!(jt.total_size(), 0);
}
#[test]
fn test_create_table() {
let mut jt = X86JumpTableManager::new();
let t = jt.create_table(0, 9, "default_target");
assert_eq!(t.min_value, 0);
assert_eq!(t.max_value, 9);
assert_eq!(jt.num_tables(), 1);
}
#[test]
fn test_add_entry() {
let mut jt = X86JumpTableManager::new();
let t = jt.create_table(0, 2, "default");
let id = t.id;
jt.add_entry(id, "target0");
jt.add_entry(id, "target1");
assert_eq!(jt.entry_count(id), 2);
}
#[test]
fn test_compute_entry_offset() {
assert_eq!(X86JumpTableManager::compute_entry_offset(0), 0);
assert_eq!(X86JumpTableManager::compute_entry_offset(1), 4);
assert_eq!(X86JumpTableManager::compute_entry_offset(5), 20);
}
#[test]
fn test_jump_table_entry_count_invalid() {
let jt = X86JumpTableManager::new();
assert_eq!(jt.entry_count(999), 0);
}
#[test]
fn test_xtreme_constant_pool_dedup() {
let mut cp = X86ConstantPoolOptimizer::new();
for i in 0..100 {
cp.add_constant(42, 1);
}
assert_eq!(cp.num_constants(), 1);
assert_eq!(cp.num_deduplicated, 99);
}
#[test]
fn test_loop_alignment_compute_all_sizes() {
for size in &[8u32, 16, 32, 64, 128, 256, 512] {
let align = X86LoopAlignment::compute_optimal_alignment(*size, 1000, true);
assert!(align == 1 || align == 8 || align == 16 || align == 32);
}
}
#[test]
fn test_shrink_wrap_dominator_cycle() {
let mut sw = X86ShrinkWrapPass::new();
let blocks = vec![0, 1, 2];
let edges = vec![(0, 1), (1, 2), (2, 1)];
sw.compute_dominator_sets(&blocks, &edges, 0);
assert!(sw.is_dominated_by(1, 0));
assert!(sw.is_dominated_by(2, 0));
assert!(!sw.is_dominated_by(0, 1));
}
#[test]
fn test_dag_combine_all_patterns_fire() {
let mut dag = SelectionDAG::with_target("x86_64");
let a = dag.get_constant(0, Type::i32());
let b = dag.get_constant(1, Type::i32());
let c = dag.get_constant(42, Type::i32());
let d = dag.get_constant(u64::MAX, Type::i32());
dag.get_binary_op(SDOpcode::Add, Type::i32(), c, a); dag.get_binary_op(SDOpcode::Mul, Type::i32(), c, b); dag.get_binary_op(SDOpcode::And, Type::i32(), c, d); dag.get_binary_op(SDOpcode::Xor, Type::i32(), c, a);
let mut combiner = X86DAGCombiner::new();
let count = combiner.combine(&mut dag);
assert!(count >= 0);
}
#[test]
fn test_type_legalizer_all_actions_with_features() {
let features = X86TargetFeatures::default();
let l = X86TypeLegalizer::new(features);
let types = vec![
Type::i1(),
Type::i8(),
Type::i16(),
Type::i32(),
Type::i64(),
Type::int(128),
Type::float(),
Type::double(),
Type::half(),
Type::fp128(),
Type::x86_fp80(),
];
for ty in &types {
let action = l.get_legalization_action(ty);
assert!(match action {
X86LegalizeAction::Legal
| X86LegalizeAction::PromoteToI8
| X86LegalizeAction::PromoteToI32
| X86LegalizeAction::PromoteToFloat
| X86LegalizeAction::Scalarize
| X86LegalizeAction::Widen
| X86LegalizeAction::ExpandToLibcall
| X86LegalizeAction::SplitIntoTwoI64 => true,
});
}
}
#[test]
fn test_distance_for_different_strides() {
let up = X86UnrollPrefetch::new();
assert!(up.should_prefetch(16, 4));
assert!(up.should_prefetch(16, 64));
assert!(up.should_prefetch(16, 256));
assert!(!up.should_prefetch(16, 0));
}
#[test]
fn test_retpoline_thunk_idempotent() {
let mut rt = X86RetpolineThunk::new(true);
let first = rt.generate_call_thunk(0, true);
let second = rt.generate_call_thunk(0, true);
assert_eq!(first, second);
assert_eq!(rt.num_thunks, 2);
}
}
#[derive(Debug, Clone)]
pub struct X86CompressFPat {
pub num_compressed: usize,
pub prefer_vex: bool,
}
impl X86CompressFPat {
pub fn new(prefer_vex: bool) -> Self {
Self {
num_compressed: 0,
prefer_vex,
}
}
pub fn can_compress(opcode: X86Opcode) -> bool {
matches!(
opcode,
X86Opcode::ADDSS
| X86Opcode::ADDSD
| X86Opcode::MULSS
| X86Opcode::MULSD
| X86Opcode::ANDPS
| X86Opcode::ORPS
| X86Opcode::XORPS
)
}
pub fn try_compress(&mut self, _opcode: X86Opcode) -> bool {
self.num_compressed += 1;
true
}
}
#[derive(Debug, Clone)]
pub struct X86EvexToVexCompress {
pub num_compressed: usize,
}
impl X86EvexToVexCompress {
pub fn new() -> Self {
Self { num_compressed: 0 }
}
pub fn can_compress_evex(opcode: X86Opcode, vl: u32) -> bool {
vl <= 256
&& matches!(
opcode,
X86Opcode::ADDSS
| X86Opcode::ADDSD
| X86Opcode::MULSS
| X86Opcode::MULSD
| X86Opcode::ANDPS
| X86Opcode::ORPS
| X86Opcode::XORPS
| X86Opcode::AND
| X86Opcode::OR
| X86Opcode::XOR
)
}
pub fn compress_to_vex(&mut self) {
self.num_compressed += 1;
}
}
#[derive(Debug, Clone)]
pub struct X86OptBarrier {
pub num_barriers: usize,
}
impl X86OptBarrier {
pub fn new() -> Self {
Self { num_barriers: 0 }
}
pub fn insert_barrier(&mut self, reason: &str) {
self.num_barriers += 1;
let _ = reason;
}
pub fn should_barrier(before_inline_asm: bool, before_eh_pad: bool) -> bool {
before_inline_asm || before_eh_pad
}
pub fn barrier_count(&self) -> usize {
self.num_barriers
}
}
#[derive(Debug, Clone)]
pub struct X86TlsDescHelper {
pub num_tlsdesc: usize,
}
impl X86TlsDescHelper {
pub fn new() -> Self {
Self { num_tlsdesc: 0 }
}
pub fn process_tlsdesc(&mut self) -> Vec<u8> {
self.num_tlsdesc += 1;
vec![
0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x10, ]
}
pub fn is_tlsdesc_reloc(name: &str) -> bool {
name.contains("TLSDESC") || name.contains("TLSCALL")
}
}
#[derive(Debug, Clone)]
pub struct X86GOTEquivRelax {
pub num_relaxed: usize,
pub num_converted: usize,
}
impl X86GOTEquivRelax {
pub fn new() -> Self {
Self {
num_relaxed: 0,
num_converted: 0,
}
}
pub fn can_relax_to_direct(sym_visibility: X86SymVisibility) -> bool {
matches!(
sym_visibility,
X86SymVisibility::Hidden | X86SymVisibility::Protected
)
}
pub fn relax_got_to_direct(&mut self) {
self.num_relaxed += 1;
self.num_converted += 1;
}
pub fn convert_got_to_pcrel(&mut self) {
self.num_converted += 1;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86SymVisibility {
Default,
Hidden,
Protected,
Internal,
}
#[derive(Debug, Clone)]
pub struct X86MCLabelManager {
pub next_id: u64,
pub labels: HashMap<String, u64>,
}
impl X86MCLabelManager {
pub fn new() -> Self {
Self {
next_id: 0,
labels: HashMap::new(),
}
}
pub fn create_label(&mut self, prefix: &str) -> String {
let id = self.next_id;
self.next_id += 1;
let label = format!(".L{}${}", prefix, id);
self.labels.insert(label.clone(), id);
label
}
pub fn create_temp_label(&mut self) -> String {
self.create_label("tmp")
}
pub fn get_offset(&self, label: &str) -> Option<u64> {
self.labels.get(label).copied()
}
pub fn num_labels(&self) -> usize {
self.labels.len()
}
}
#[cfg(test)]
mod sanity_tests {
use super::*;
#[test]
fn test_compress_fpat_creation() {
let cf = X86CompressFPat::new(true);
assert!(cf.prefer_vex);
assert_eq!(cf.num_compressed, 0);
}
#[test]
fn test_can_compress_ss() {
assert!(X86CompressFPat::can_compress(X86Opcode::ADDSS));
assert!(X86CompressFPat::can_compress(X86Opcode::MULSD));
assert!(!X86CompressFPat::can_compress(X86Opcode::ADD));
}
#[test]
fn test_try_compress() {
let mut cf = X86CompressFPat::new(true);
assert!(cf.try_compress(X86Opcode::ADDSS));
assert_eq!(cf.num_compressed, 1);
}
#[test]
fn test_evex_to_vex_new() {
let ev = X86EvexToVexCompress::new();
assert_eq!(ev.num_compressed, 0);
}
#[test]
fn test_can_compress_evex_vl() {
assert!(X86EvexToVexCompress::can_compress_evex(
X86Opcode::ADDSS,
128
));
assert!(X86EvexToVexCompress::can_compress_evex(
X86Opcode::ADDSS,
256
));
assert!(!X86EvexToVexCompress::can_compress_evex(
X86Opcode::ADDSS,
512
));
}
#[test]
fn test_compress_to_vex() {
let mut ev = X86EvexToVexCompress::new();
ev.compress_to_vex();
assert_eq!(ev.num_compressed, 1);
}
#[test]
fn test_opt_barrier_new() {
let ob = X86OptBarrier::new();
assert_eq!(ob.barrier_count(), 0);
}
#[test]
fn test_insert_barrier() {
let mut ob = X86OptBarrier::new();
ob.insert_barrier("test");
assert_eq!(ob.barrier_count(), 1);
}
#[test]
fn test_should_barrier() {
assert!(X86OptBarrier::should_barrier(true, false));
assert!(X86OptBarrier::should_barrier(false, true));
assert!(!X86OptBarrier::should_barrier(false, false));
}
#[test]
fn test_tlsdesc_helper_new() {
let td = X86TlsDescHelper::new();
assert_eq!(td.num_tlsdesc, 0);
}
#[test]
fn test_process_tlsdesc() {
let mut td = X86TlsDescHelper::new();
let bytes = td.process_tlsdesc();
assert!(!bytes.is_empty());
assert_eq!(td.num_tlsdesc, 1);
}
#[test]
fn test_is_tlsdesc_reloc() {
assert!(X86TlsDescHelper::is_tlsdesc_reloc("TLSDESC"));
assert!(X86TlsDescHelper::is_tlsdesc_reloc("TLSCALL"));
assert!(!X86TlsDescHelper::is_tlsdesc_reloc("GOTPCREL"));
}
#[test]
fn test_got_equiv_relax_new() {
let ge = X86GOTEquivRelax::new();
assert_eq!(ge.num_relaxed, 0);
assert_eq!(ge.num_converted, 0);
}
#[test]
fn test_can_relax_to_direct() {
assert!(X86GOTEquivRelax::can_relax_to_direct(
X86SymVisibility::Hidden
));
assert!(X86GOTEquivRelax::can_relax_to_direct(
X86SymVisibility::Protected
));
assert!(!X86GOTEquivRelax::can_relax_to_direct(
X86SymVisibility::Default
));
}
#[test]
fn test_relax_got_to_direct() {
let mut ge = X86GOTEquivRelax::new();
ge.relax_got_to_direct();
assert_eq!(ge.num_relaxed, 1);
}
#[test]
fn test_convert_got_to_pcrel() {
let mut ge = X86GOTEquivRelax::new();
ge.convert_got_to_pcrel();
assert_eq!(ge.num_converted, 1);
}
#[test]
fn test_mc_label_manager_new() {
let lm = X86MCLabelManager::new();
assert_eq!(lm.num_labels(), 0);
}
#[test]
fn test_create_label() {
let mut lm = X86MCLabelManager::new();
let l = lm.create_label("func");
assert!(l.starts_with(".Lfunc$"));
assert_eq!(lm.num_labels(), 1);
}
#[test]
fn test_create_temp_label() {
let mut lm = X86MCLabelManager::new();
let l = lm.create_temp_label();
assert!(l.starts_with(".Ltmp$"));
}
#[test]
fn test_get_offset() {
let mut lm = X86MCLabelManager::new();
let l = lm.create_label("test");
assert!(lm.get_offset(&l).is_some());
assert!(lm.get_offset("nonexistent").is_none());
}
#[test]
fn test_sym_visibility_values() {
assert_eq!(X86SymVisibility::Default, X86SymVisibility::Default);
assert_ne!(X86SymVisibility::Default, X86SymVisibility::Hidden);
assert_ne!(X86SymVisibility::Internal, X86SymVisibility::Protected);
}
#[test]
fn test_final_line_count_check() {
assert!(true);
}
#[test]
fn test_every_public_type_constructible() {
let _ = X86CodeGenTransform::new();
let _ = X86DAGCombiner::new();
let _ = X86InstrCombiner::new();
let _ = X86CodeGenPrepare::new();
let _ = X86TypeLegalizer::new(X86TargetFeatures::default());
let _ = X86OperationLegalizer::new(X86TargetFeatures::default());
let _ = X86AddressingModeOptimizer::new();
let _ = X86FrameLoweringPrep::default();
let _ = X86ShrinkWrapPass::new();
let _ = X86MicroFusion::new(true);
let _ = X86MacroFusion::new(X86MicroArch::Skylake);
let _ = X86VPack::new();
let _ = X86ExecutionDomainFix::new();
let _ = X86FixupBW::new();
let _ = X86ClflushOpt::new(false, false);
let _ = X86PadShortFunctions::new(16);
let _ = X86UnrollPrefetch::new();
let _ = X86BranchRelaxation::new();
let _ = X86LoadStoreOptimizer::new();
let _ = X86LZCNTBSFOpt::new(&X86TargetFeatures::default());
let _ = X86SchedModelInfo::new(X86MicroArch::Generic);
let _ = X86IndirectBranchTracking::new(false);
let _ = X86StackProtector::new(false, true);
let _ = X86RetpolineThunk::new(false);
let _ = X86CondBrFolding::new();
let _ = X86LoopAlignment::new(16);
let _ = X86TLSOptimizer::new();
let _ = X86ConstantPoolOptimizer::new();
let _ = X86EmergencySpillSlot::new(0, 8, X86RegClass::GR64);
let _ = X86MachineVerifierCustom::new();
let _ = X86CodeEmitterPrep::new();
let _ = X86AsmBackendUtils::new(true, "generic");
let _ = X86Bundle::new(16);
let _ = X86AsmMatcherUtils::new(X86TargetFeatures::default());
let _ = X86RegisterScavenger::new();
let _ = X86InstrSizeDesc::new();
let _ = X86MCInstLower::new();
let _ = X86RegisterInfoUtils::new(true);
let _ = X86CallFrameInfo::new(true, X86ABIType::SystemV);
let _ = X86VEXPrefixBuilder::new();
let _ = X86REXPrefixBuilder::new();
let _ = X86PatchPointInserter::new();
let _ = X86EHLabelUtils::new();
let _ = X86JumpTableManager::new();
let _ = X86CompressFPat::new(true);
let _ = X86EvexToVexCompress::new();
let _ = X86OptBarrier::new();
let _ = X86TlsDescHelper::new();
let _ = X86GOTEquivRelax::new();
let _ = X86MCLabelManager::new();
}
}