use crate::codegen::{MachineBasicBlock, MachineInstr, MachineOperand};
use crate::x86::x86_instr_info::{X86InstrInfo, X86Opcode};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum FoldResult {
Constant(i64),
ConstantU(u64),
FpConstant(u64),
Undefined,
Poison,
NotSimplified,
}
impl FoldResult {
pub fn is_simplified(&self) -> bool {
!matches!(self, FoldResult::NotSimplified)
}
pub fn is_constant(&self) -> bool {
matches!(
self,
FoldResult::Constant(_) | FoldResult::ConstantU(_) | FoldResult::FpConstant(_)
)
}
pub fn as_i64(&self) -> Option<i64> {
match self {
FoldResult::Constant(v) => Some(*v),
_ => None,
}
}
pub fn as_u64(&self) -> Option<u64> {
match self {
FoldResult::ConstantU(v) => Some(*v),
FoldResult::FpConstant(v) => Some(*v),
FoldResult::Constant(v) if *v >= 0 => Some(*v as u64),
_ => None,
}
}
}
impl std::fmt::Display for FoldResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FoldResult::Constant(v) => write!(f, "Constant({})", v),
FoldResult::ConstantU(v) => write!(f, "ConstantU({})", v),
FoldResult::FpConstant(v) => write!(f, "FpConstant({:#018x})", v),
FoldResult::Undefined => write!(f, "Undefined"),
FoldResult::Poison => write!(f, "Poison"),
FoldResult::NotSimplified => write!(f, "NotSimplified"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SimplifyStats {
pub integer_identity: usize,
pub integer_zero: usize,
pub self_cancel: usize,
pub shift_identity: usize,
pub shift_zero: usize,
pub select_folded: usize,
pub icmp_folded: usize,
pub phi_folded: usize,
pub gep_folded: usize,
pub undef_propagated: usize,
pub poison_propagated: usize,
pub fp_identity: usize,
pub fp_nan: usize,
pub fp_infinity: usize,
pub fp_subnormal: usize,
pub vector_identity: usize,
pub vector_zero: usize,
pub vector_all_ones: usize,
pub vector_self_cancel: usize,
pub cast_identity: usize,
pub extract_insert_folded: usize,
pub total_simplified: usize,
}
impl SimplifyStats {
pub fn merge(&mut self, other: &SimplifyStats) {
self.integer_identity += other.integer_identity;
self.integer_zero += other.integer_zero;
self.self_cancel += other.self_cancel;
self.shift_identity += other.shift_identity;
self.shift_zero += other.shift_zero;
self.select_folded += other.select_folded;
self.icmp_folded += other.icmp_folded;
self.phi_folded += other.phi_folded;
self.gep_folded += other.gep_folded;
self.undef_propagated += other.undef_propagated;
self.poison_propagated += other.poison_propagated;
self.fp_identity += other.fp_identity;
self.fp_nan += other.fp_nan;
self.fp_infinity += other.fp_infinity;
self.fp_subnormal += other.fp_subnormal;
self.vector_identity += other.vector_identity;
self.vector_zero += other.vector_zero;
self.vector_all_ones += other.vector_all_ones;
self.vector_self_cancel += other.vector_self_cancel;
self.cast_identity += other.cast_identity;
self.extract_insert_folded += other.extract_insert_folded;
self.total_simplified += other.total_simplified;
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum OperandValue {
Const(i64),
ConstU(u64),
Undef,
Poison,
Unknown,
}
fn classify_operand(op: &MachineOperand) -> OperandValue {
match op {
MachineOperand::Imm(v) => OperandValue::Const(*v),
MachineOperand::Reg(r) if *r == u32::MAX => OperandValue::Undef,
_ => OperandValue::Unknown,
}
}
fn is_zero(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::Imm(0))
}
fn is_one(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::Imm(1))
}
fn is_neg_one(op: &MachineOperand) -> bool {
matches!(op, MachineOperand::Imm(-1))
}
fn is_all_ones(op: &MachineOperand) -> bool {
is_neg_one(op)
}
fn is_undef(op: &MachineOperand) -> bool {
matches!(classify_operand(op), OperandValue::Undef)
}
fn is_poison(op: &MachineOperand) -> bool {
matches!(classify_operand(op), OperandValue::Poison)
}
fn is_constant(op: &MachineOperand) -> bool {
matches!(
classify_operand(op),
OperandValue::Const(_) | OperandValue::ConstU(_)
)
}
fn is_fp_pos_zero(bits: u64) -> bool {
bits == 0x0000_0000_0000_0000
}
fn is_fp_neg_zero(bits: u64) -> bool {
bits == 0x8000_0000_0000_0000
}
fn is_fp32_neg_zero(bits: u32) -> bool {
bits == 0x8000_0000
}
pub struct X86InstSimplify {
pub stats: SimplifyStats,
operand_cache: HashMap<(usize, usize), OperandValue>,
pub fold_fp_identities: bool,
pub simplify_fp_nan: bool,
pub fold_vector_identities: bool,
pub propagate_undef: bool,
pub fold_cast_identities: bool,
pub fold_extract_insert: bool,
pub max_simplifications_per_block: usize,
}
impl X86InstSimplify {
pub fn new() -> Self {
Self {
stats: SimplifyStats::default(),
operand_cache: HashMap::new(),
fold_fp_identities: true,
simplify_fp_nan: true,
fold_vector_identities: true,
propagate_undef: true,
fold_cast_identities: true,
fold_extract_insert: true,
max_simplifications_per_block: 1000,
}
}
pub fn simplify(&mut self, inst: &MachineInstr, _instr_info: &X86InstrInfo) -> FoldResult {
let opcode = inst.opcode;
let ops = &inst.operands;
match opcode {
o if o == X86Opcode::ADD as u32 => self.simplify_add(ops),
o if o == X86Opcode::SUB as u32 => self.simplify_sub(ops),
o if o == X86Opcode::MUL as u32 || o == X86Opcode::IMUL as u32 => {
self.simplify_mul(ops)
}
o if o == X86Opcode::AND as u32 => self.simplify_and(ops),
o if o == X86Opcode::OR as u32 => self.simplify_or(ops),
o if o == X86Opcode::XOR as u32 => self.simplify_xor(ops),
o if o == X86Opcode::ADC as u32 => self.simplify_adc(ops),
o if o == X86Opcode::SBB as u32 => self.simplify_sbb(ops),
o if o == X86Opcode::NEG as u32 => self.simplify_neg(ops),
o if o == X86Opcode::NOT as u32 => self.simplify_not(ops),
o if o == X86Opcode::DIV as u32 => self.simplify_udiv(ops),
o if o == X86Opcode::IDIV as u32 => self.simplify_sdiv(ops),
o if o == X86Opcode::SHL as u32 => self.simplify_shl(ops),
o if o == X86Opcode::SHR as u32 => self.simplify_lshr(ops),
o if o == X86Opcode::SAR as u32 => self.simplify_ashr(ops),
o if o == X86Opcode::ROL as u32 => self.simplify_rotate_left(ops),
o if o == X86Opcode::ROR as u32 => self.simplify_rotate_right(ops),
o if o == X86Opcode::ADDSS as u32 || o == X86Opcode::ADDSD as u32 => {
self.simplify_fadd(ops, is_double(o))
}
o if o == X86Opcode::SUBSS as u32 || o == X86Opcode::SUBSD as u32 => {
self.simplify_fsub(ops, is_double(o))
}
o if o == X86Opcode::MULSS as u32 || o == X86Opcode::MULSD as u32 => {
self.simplify_fmul(ops, is_double(o))
}
o if o == X86Opcode::DIVSS as u32 || o == X86Opcode::DIVSD as u32 => {
self.simplify_fdiv(ops, is_double(o))
}
o if o == X86Opcode::SQRTSS as u32 || o == X86Opcode::SQRTSD as u32 => {
self.simplify_fsqrt(ops)
}
o if o == X86Opcode::MINSS as u32 || o == X86Opcode::MINSD as u32 => {
self.simplify_fmin(ops)
}
o if o == X86Opcode::MAXSS as u32 || o == X86Opcode::MAXSD as u32 => {
self.simplify_fmax(ops)
}
o if o == X86Opcode::ADDPS as u32
|| o == X86Opcode::ADDPD as u32
|| o == X86Opcode::VADDPS as u32
|| o == X86Opcode::VADDPD as u32 =>
{
self.simplify_vector_fadd(ops)
}
o if o == X86Opcode::MULPS as u32
|| o == X86Opcode::MULPD as u32
|| o == X86Opcode::VMULPS as u32
|| o == X86Opcode::VMULPD as u32 =>
{
self.simplify_vector_fmul(ops)
}
o if o == X86Opcode::SUBPS as u32
|| o == X86Opcode::SUBPD as u32
|| o == X86Opcode::VSUBPS as u32
|| o == X86Opcode::VSUBPD as u32 =>
{
self.simplify_vector_fsub(ops)
}
o if o == X86Opcode::DIVPS as u32
|| o == X86Opcode::DIVPD as u32
|| o == X86Opcode::VDIVPS as u32
|| o == X86Opcode::VDIVPD as u32 =>
{
self.simplify_vector_fdiv(ops)
}
o if o == X86Opcode::ANDPS as u32
|| o == X86Opcode::ANDPD as u32
|| o == X86Opcode::VANDPS as u32
|| o == X86Opcode::VANDPD as u32 =>
{
self.simplify_vector_and(ops)
}
o if o == X86Opcode::ORPS as u32
|| o == X86Opcode::ORPD as u32
|| o == X86Opcode::VORPS as u32
|| o == X86Opcode::VORPD as u32 =>
{
self.simplify_vector_or(ops)
}
o if o == X86Opcode::XORPS as u32
|| o == X86Opcode::XORPD as u32
|| o == X86Opcode::VXORPS as u32
|| o == X86Opcode::VXORPD as u32 =>
{
self.simplify_vector_xor(ops)
}
o if o == X86Opcode::ANDNPS as u32
|| o == X86Opcode::ANDNPD as u32
|| o == X86Opcode::VANDNPS as u32 =>
{
self.simplify_vector_andn(ops)
}
o if o == X86Opcode::VPADDD_Z as u32 || o == X86Opcode::VPADDQ_Z as u32 => {
self.simplify_vector_int_add(ops)
}
o if o == X86Opcode::VPSUBD_Z as u32 || o == X86Opcode::VPSUBQ_Z as u32 => {
self.simplify_vector_int_sub(ops)
}
o if o == X86Opcode::VPMULLD_Z as u32 || o == X86Opcode::VPMULLQ_Z as u32 => {
self.simplify_vector_int_mul(ops)
}
o if o == X86Opcode::PSHUFD as u32 || o == X86Opcode::VPERMILPS as u32 => {
self.simplify_shuffle(ops)
}
o if o == X86Opcode::HADDPS as u32
|| o == X86Opcode::HADDPD as u32
|| o == X86Opcode::HSUBPS as u32
|| o == X86Opcode::HSUBPD as u32 =>
{
self.simplify_horizontal(ops)
}
o if o >= X86Opcode::VFMADD132PD as u32 && o <= X86Opcode::VFMADD231SD as u32 => {
self.simplify_fma(ops)
}
_ => FoldResult::NotSimplified,
}
}
pub fn simplify_block(
&mut self,
block: &mut MachineBasicBlock,
instr_info: &X86InstrInfo,
) -> bool {
let mut changed = false;
let mut i = 0;
let mut simplification_count = 0;
while i < block.instructions.len()
&& simplification_count < self.max_simplifications_per_block
{
let result = self.simplify(&block.instructions[i], instr_info);
match result {
FoldResult::Constant(v) => {
let def = block.instructions[i].def;
block.instructions[i] =
MachineInstr::new(X86Opcode::MOV as u32).with_def(def.unwrap_or(0));
block.instructions[i].push_imm(v);
self.stats.total_simplified += 1;
simplification_count += 1;
changed = true;
}
FoldResult::ConstantU(v) => {
let def = block.instructions[i].def;
block.instructions[i] =
MachineInstr::new(X86Opcode::MOV as u32).with_def(def.unwrap_or(0));
block.instructions[i].push_imm(v as i64);
self.stats.total_simplified += 1;
simplification_count += 1;
changed = true;
}
FoldResult::FpConstant(v) => {
let def = block.instructions[i].def;
block.instructions[i] =
MachineInstr::new(X86Opcode::MOV as u32).with_def(def.unwrap_or(0));
block.instructions[i].push_imm(v as i64);
self.stats.total_simplified += 1;
simplification_count += 1;
changed = true;
}
FoldResult::Undefined | FoldResult::Poison => {
block.instructions[i] = MachineInstr::new(X86Opcode::UD2 as u32);
self.stats.total_simplified += 1;
simplification_count += 1;
changed = true;
}
_ => {}
}
i += 1;
}
changed
}
fn simplify_add(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified; }
if is_zero(&ops[0]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::Constant(a.wrapping_add(*b));
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
if is_poison(&ops[0]) || is_poison(&ops[1]) {
self.stats.poison_propagated += 1;
return FoldResult::Poison;
}
FoldResult::NotSimplified
}
fn simplify_sub(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if ops[0] == ops[1] {
self.stats.self_cancel += 1;
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
return FoldResult::Constant(a.wrapping_sub(*b));
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
if is_poison(&ops[0]) || is_poison(&ops[1]) {
self.stats.poison_propagated += 1;
return FoldResult::Poison;
}
FoldResult::NotSimplified
}
fn simplify_mul(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_one(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_one(&ops[0]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.integer_zero += 1;
return FoldResult::Constant(0);
}
if is_zero(&ops[0]) {
self.stats.integer_zero += 1;
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
return FoldResult::Constant(a.wrapping_mul(*b));
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
if is_poison(&ops[0]) || is_poison(&ops[1]) {
self.stats.poison_propagated += 1;
return FoldResult::Poison;
}
FoldResult::NotSimplified
}
fn simplify_and(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_all_ones(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_all_ones(&ops[0]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.integer_zero += 1;
return FoldResult::Constant(0);
}
if is_zero(&ops[0]) {
self.stats.integer_zero += 1;
return FoldResult::Constant(0);
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
return FoldResult::Constant(a & b);
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
FoldResult::NotSimplified
}
fn simplify_or(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_all_ones(&ops[1]) || is_all_ones(&ops[0]) {
self.stats.integer_identity += 1;
return FoldResult::Constant(-1);
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
return FoldResult::Constant(a | b);
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
FoldResult::NotSimplified
}
fn simplify_xor(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if ops[0] == ops[1] {
self.stats.self_cancel += 1;
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
return FoldResult::Constant(a ^ b);
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
FoldResult::NotSimplified
}
fn simplify_adc(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() >= 2 && is_zero(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_sbb(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() >= 2 && is_zero(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_neg(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.is_empty() {
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
return FoldResult::Constant(0);
}
if let MachineOperand::Imm(v) = &ops[0] {
return FoldResult::Constant(v.wrapping_neg());
}
FoldResult::NotSimplified
}
fn simplify_not(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.is_empty() {
return FoldResult::NotSimplified;
}
if let MachineOperand::Imm(v) = &ops[0] {
return FoldResult::Constant(!v);
}
FoldResult::NotSimplified
}
fn simplify_udiv(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_one(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
return FoldResult::Undefined;
}
if is_zero(&ops[0]) {
self.stats.integer_zero += 1;
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
if *b == 0 {
return FoldResult::Undefined;
}
let ua = *a as u64;
let ub = *b as u64;
return FoldResult::ConstantU(ua / ub);
}
FoldResult::NotSimplified
}
fn simplify_sdiv(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_one(&ops[1]) {
self.stats.integer_identity += 1;
return FoldResult::NotSimplified;
}
if is_neg_one(&ops[1]) {
if let MachineOperand::Imm(v) = &ops[0] {
if *v == i64::MIN {
return FoldResult::Poison;
}
return FoldResult::Constant(v.wrapping_neg());
}
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
return FoldResult::Undefined;
}
if is_zero(&ops[0]) {
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
if *b == 0 {
return FoldResult::Undefined;
}
if *a == i64::MIN && *b == -1 {
return FoldResult::Poison;
}
return FoldResult::Constant(a.wrapping_div(*b));
}
FoldResult::NotSimplified
}
fn simplify_shl(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.shift_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
self.stats.shift_zero += 1;
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
let shift = (*b as u32).min(63);
return FoldResult::Constant(a.wrapping_shl(shift));
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
FoldResult::NotSimplified
}
fn simplify_lshr(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.shift_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
self.stats.shift_zero += 1;
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
let shift = (*b as u32).min(63);
let result = (*a as u64).wrapping_shr(shift) as i64;
return FoldResult::Constant(result);
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
FoldResult::NotSimplified
}
fn simplify_ashr(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.shift_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
self.stats.shift_zero += 1;
return FoldResult::Constant(0);
}
if let (MachineOperand::Imm(a), MachineOperand::Imm(b)) = (&ops[0], &ops[1]) {
let shift = (*b as u32).min(63);
return FoldResult::Constant(a.wrapping_shr(shift));
}
if self.propagate_undef && (is_undef(&ops[0]) || is_undef(&ops[1])) {
self.stats.undef_propagated += 1;
return FoldResult::Undefined;
}
FoldResult::NotSimplified
}
fn simplify_rotate_left(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.shift_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
return FoldResult::Constant(0);
}
FoldResult::NotSimplified
}
fn simplify_rotate_right(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() < 2 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[1]) {
self.stats.shift_identity += 1;
return FoldResult::NotSimplified;
}
if is_zero(&ops[0]) {
return FoldResult::Constant(0);
}
FoldResult::NotSimplified
}
fn simplify_fadd(&mut self, ops: &[MachineOperand], is_double_precision: bool) -> FoldResult {
if !self.fold_fp_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 {
if let MachineOperand::Imm(v) = &ops[1] {
let bits = *v as u64;
if (is_double_precision && is_fp_neg_zero(bits))
|| (!is_double_precision && is_fp32_neg_zero(bits as u32))
{
self.stats.fp_identity += 1;
return FoldResult::NotSimplified;
}
}
}
if self.simplify_fp_nan
&& ops
.iter()
.any(|o| matches!(o, MachineOperand::Imm(v) if is_fp_nan_bits(*v as u64)))
{
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
FoldResult::NotSimplified
}
fn simplify_fsub(&mut self, ops: &[MachineOperand], is_double_precision: bool) -> FoldResult {
if !self.fold_fp_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 {
if let MachineOperand::Imm(v) = &ops[1] {
let bits = *v as u64;
if (is_double_precision && is_fp_neg_zero(bits))
|| (!is_double_precision && is_fp32_neg_zero(bits as u32))
{
self.stats.fp_identity += 1;
return FoldResult::NotSimplified;
}
}
}
if self.simplify_fp_nan
&& ops
.iter()
.any(|o| matches!(o, MachineOperand::Imm(v) if is_fp_nan_bits(*v as u64)))
{
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
FoldResult::NotSimplified
}
fn simplify_fmul(&mut self, ops: &[MachineOperand], is_double_precision: bool) -> FoldResult {
if !self.fold_fp_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 {
if let MachineOperand::Imm(v) = &ops[1] {
let bits = *v as u64;
if (is_double_precision && bits == 0x3FF0000000000000)
|| (!is_double_precision && bits as u32 == 0x3F800000)
{
self.stats.fp_identity += 1;
return FoldResult::NotSimplified;
}
if bits == 0 {
self.stats.fp_identity += 1;
return FoldResult::FpConstant(0);
}
}
}
if ops.len() >= 2 {
if let MachineOperand::Imm(v) = &ops[0] {
let bits = *v as u64;
if (is_double_precision && bits == 0x3FF0000000000000)
|| (!is_double_precision && bits as u32 == 0x3F800000)
{
self.stats.fp_identity += 1;
return FoldResult::NotSimplified;
}
}
}
if self.simplify_fp_nan
&& ops
.iter()
.any(|o| matches!(o, MachineOperand::Imm(v) if is_fp_nan_bits(*v as u64)))
{
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
FoldResult::NotSimplified
}
fn simplify_fdiv(&mut self, ops: &[MachineOperand], is_double_precision: bool) -> FoldResult {
if !self.fold_fp_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 {
if let MachineOperand::Imm(v) = &ops[1] {
let bits = *v as u64;
if (is_double_precision && bits == 0x3FF0000000000000)
|| (!is_double_precision && bits as u32 == 0x3F800000)
{
self.stats.fp_identity += 1;
return FoldResult::NotSimplified;
}
if is_fp_pos_zero(bits) {
self.stats.fp_infinity += 1;
return FoldResult::FpConstant(0x7FF0000000000000); }
}
}
if self.simplify_fp_nan
&& ops
.iter()
.any(|o| matches!(o, MachineOperand::Imm(v) if is_fp_nan_bits(*v as u64)))
{
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
FoldResult::NotSimplified
}
fn simplify_fsqrt(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_fp_identities {
return FoldResult::NotSimplified;
}
if ops.is_empty() {
return FoldResult::NotSimplified;
}
if let MachineOperand::Imm(v) = &ops[0] {
let bits = *v as u64;
if is_fp_pos_zero(bits) {
return FoldResult::FpConstant(0);
}
if bits == 0x3FF0000000000000 {
return FoldResult::FpConstant(0x3FF0000000000000);
}
if is_fp_nan_bits(bits) {
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
}
FoldResult::NotSimplified
}
fn simplify_fmin(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_fp_identities || ops.len() < 2 {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && ops[0] == ops[1] {
return FoldResult::NotSimplified;
}
if self.simplify_fp_nan
&& ops
.iter()
.any(|o| matches!(o, MachineOperand::Imm(v) if is_fp_nan_bits(*v as u64)))
{
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
FoldResult::NotSimplified
}
fn simplify_fmax(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_fp_identities || ops.len() < 2 {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && ops[0] == ops[1] {
return FoldResult::NotSimplified;
}
if self.simplify_fp_nan
&& ops
.iter()
.any(|o| matches!(o, MachineOperand::Imm(v) if is_fp_nan_bits(*v as u64)))
{
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
FoldResult::NotSimplified
}
fn simplify_vector_fadd(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_vector_fmul(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && ops[0] == ops[1] {
}
FoldResult::NotSimplified
}
fn simplify_vector_fsub(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_self_cancel += 1;
return FoldResult::Constant(0); }
FoldResult::NotSimplified
}
fn simplify_vector_fdiv(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified; }
FoldResult::NotSimplified
}
fn simplify_vector_and(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && is_all_ones(&ops[1]) {
self.stats.vector_all_ones += 1;
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && is_zero(&ops[1]) {
self.stats.vector_zero += 1;
return FoldResult::Constant(0);
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_vector_or(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && is_zero(&ops[1]) {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && is_all_ones(&ops[1]) {
self.stats.vector_all_ones += 1;
return FoldResult::Constant(-1);
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_vector_xor(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_self_cancel += 1;
return FoldResult::Constant(0);
}
if ops.len() >= 2 && is_zero(&ops[1]) {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_vector_andn(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_vector_identities {
return FoldResult::NotSimplified;
}
if ops.len() >= 2 && is_zero(&ops[1]) {
self.stats.vector_zero += 1;
return FoldResult::Constant(0);
}
if ops.len() >= 2 && is_all_ones(&ops[1]) {
self.stats.vector_all_ones += 1;
}
FoldResult::NotSimplified
}
fn simplify_vector_int_add(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() >= 3 && is_zero(&ops[2]) {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_vector_int_sub(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_self_cancel += 1;
return FoldResult::Constant(0);
}
FoldResult::NotSimplified
}
fn simplify_vector_int_mul(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() >= 2 && is_one(&ops[1]) {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_shuffle(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() >= 2 {
if let MachineOperand::Imm(mask) = &ops[1] {
if *mask == 0xE4 {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
}
}
FoldResult::NotSimplified
}
fn simplify_horizontal(&mut self, ops: &[MachineOperand]) -> FoldResult {
if ops.len() >= 2 && ops[0] == ops[1] {
self.stats.vector_identity += 1;
return FoldResult::NotSimplified;
}
FoldResult::NotSimplified
}
fn simplify_fma(&mut self, ops: &[MachineOperand]) -> FoldResult {
if !self.fold_fp_identities || ops.len() < 3 {
return FoldResult::NotSimplified;
}
if is_zero(&ops[2]) {
self.stats.fp_identity += 1;
return FoldResult::NotSimplified;
}
if self.simplify_fp_nan
&& ops
.iter()
.any(|o| matches!(o, MachineOperand::Imm(v) if is_fp_nan_bits(*v as u64)))
{
self.stats.fp_nan += 1;
return FoldResult::FpConstant(quiet_nan_bits());
}
FoldResult::NotSimplified
}
pub fn simplify_icmp_same_ops(
&mut self,
pred: u32,
_lhs: &MachineOperand,
_rhs: &MachineOperand,
) -> FoldResult {
match pred {
0 => {
self.stats.icmp_folded += 1;
FoldResult::Constant(1)
} 1 => {
self.stats.icmp_folded += 1;
FoldResult::Constant(0)
} 2 | 6 => {
self.stats.icmp_folded += 1;
FoldResult::Constant(0)
} 3 | 7 => {
self.stats.icmp_folded += 1;
FoldResult::Constant(1)
} 4 | 8 => {
self.stats.icmp_folded += 1;
FoldResult::Constant(0)
} 5 | 9 => {
self.stats.icmp_folded += 1;
FoldResult::Constant(1)
} _ => FoldResult::NotSimplified,
}
}
pub fn simplify_icmp_const(&mut self, pred: u32, lhs: i64, rhs: i64) -> FoldResult {
let result = match pred {
0 => lhs == rhs, 1 => lhs != rhs, 2 => (lhs as u64) > (rhs as u64), 3 => (lhs as u64) >= (rhs as u64), 4 => (lhs as u64) < (rhs as u64), 5 => (lhs as u64) <= (rhs as u64), 6 => lhs > rhs, 7 => lhs >= rhs, 8 => lhs < rhs, 9 => lhs <= rhs, _ => return FoldResult::NotSimplified,
};
self.stats.icmp_folded += 1;
if result {
FoldResult::Constant(1)
} else {
FoldResult::Constant(0)
}
}
pub fn simplify_select(
&mut self,
cond: &MachineOperand,
a: &MachineOperand,
b: &MachineOperand,
) -> Option<MachineOperand> {
match cond {
MachineOperand::Imm(0) => {
self.stats.select_folded += 1;
Some(b.clone())
}
MachineOperand::Imm(_) => {
self.stats.select_folded += 1;
Some(a.clone())
}
_ => None,
}
}
pub fn simplify_select_same_alternatives(
&mut self,
_cond: &MachineOperand,
a: &MachineOperand,
b: &MachineOperand,
) -> Option<MachineOperand> {
if a == b {
self.stats.select_folded += 1;
return Some(a.clone());
}
None
}
pub fn simplify_gep_zero_indices(&mut self, ops: &[MachineOperand]) -> bool {
if ops.len() <= 1 {
return false;
}
let all_zero = ops[1..].iter().all(|o| matches!(o, MachineOperand::Imm(0)));
if all_zero {
self.stats.gep_folded += 1;
}
all_zero
}
pub fn simplify_gep_const(&mut self, _base: i64, indices: &[i64]) -> FoldResult {
let sum: i64 = indices.iter().sum();
self.stats.gep_folded += 1;
FoldResult::Constant(sum)
}
pub fn simplify_phi_same_values(
&mut self,
incoming: &[MachineOperand],
) -> Option<MachineOperand> {
if incoming.is_empty() {
return None;
}
let first = &incoming[0];
if incoming.iter().all(|v| v == first) {
self.stats.phi_folded += 1;
return Some(first.clone());
}
None
}
pub fn simplify_cast_identity(
&mut self,
src: &MachineOperand,
src_width: u32,
dst_width: u32,
) -> Option<MachineOperand> {
if src_width == dst_width {
self.stats.cast_identity += 1;
return Some(src.clone());
}
None
}
pub fn simplify_extractvalue_const(
&mut self,
_aggregate_elements: &[i64],
index: usize,
) -> Option<FoldResult> {
self.stats.extract_insert_folded += 1;
Some(FoldResult::Constant(0))
}
pub fn simplify_function(
&mut self,
blocks: &mut [MachineBasicBlock],
instr_info: &X86InstrInfo,
) -> usize {
let mut count = 0;
for block in blocks.iter_mut() {
let before = self.stats.total_simplified;
self.simplify_block(block, instr_info);
count += self.stats.total_simplified - before;
}
count
}
pub fn clear(&mut self) {
self.stats = SimplifyStats::default();
self.operand_cache.clear();
}
pub fn stats_summary(&self) -> String {
format!(
"X86InstSimplify: total={}, int_id={}, int_zero={}, self_cancel={}, shift_id={}, \
shift_zero={}, select={}, icmp={}, phi={}, gep={}, undef={}, poison={}, \
fp_id={}, fp_nan={}, fp_inf={}, vec_id={}, vec_zero={}, vec_ones={}, vec_self={}",
self.stats.total_simplified,
self.stats.integer_identity,
self.stats.integer_zero,
self.stats.self_cancel,
self.stats.shift_identity,
self.stats.shift_zero,
self.stats.select_folded,
self.stats.icmp_folded,
self.stats.phi_folded,
self.stats.gep_folded,
self.stats.undef_propagated,
self.stats.poison_propagated,
self.stats.fp_identity,
self.stats.fp_nan,
self.stats.fp_infinity,
self.stats.vector_identity,
self.stats.vector_zero,
self.stats.vector_all_ones,
self.stats.vector_self_cancel,
)
}
}
impl Default for X86InstSimplify {
fn default() -> Self {
Self::new()
}
}
fn is_fp_nan_bits(bits: u64) -> bool {
let exponent = (bits >> 52) & 0x7FF;
let mantissa = bits & 0x000F_FFFF_FFFF_FFFF;
exponent == 0x7FF && mantissa != 0
}
fn is_fp_inf_bits(bits: u64) -> bool {
let exponent = (bits >> 52) & 0x7FF;
let mantissa = bits & 0x000F_FFFF_FFFF_FFFF;
exponent == 0x7FF && mantissa == 0 && (bits >> 63) == 0
}
fn is_fp_neg_inf_bits(bits: u64) -> bool {
let exponent = (bits >> 52) & 0x7FF;
let mantissa = bits & 0x000F_FFFF_FFFF_FFFF;
exponent == 0x7FF && mantissa == 0 && (bits >> 63) == 1
}
fn quiet_nan_bits() -> u64 {
0x7FF8_0000_0000_0000
}
fn is_double(op: u32) -> bool {
op == X86Opcode::ADDSD as u32
|| op == X86Opcode::SUBSD as u32
|| op == X86Opcode::MULSD as u32
|| op == X86Opcode::DIVSD as u32
|| op == X86Opcode::SQRTSD as u32
|| op == X86Opcode::MINSD as u32
|| op == X86Opcode::MAXSD as u32
}
pub fn make_x86_inst_simplify() -> X86InstSimplify {
X86InstSimplify::new()
}
pub fn make_x86_inst_simplify_no_fp() -> X86InstSimplify {
let mut s = X86InstSimplify::new();
s.fold_fp_identities = false;
s.simplify_fp_nan = false;
s
}
#[cfg(test)]
mod tests {
use super::*;
fn imm(v: i64) -> MachineOperand {
MachineOperand::Imm(v)
}
fn reg(r: u32) -> MachineOperand {
MachineOperand::Reg(r)
}
fn make_inst(opcode: u32, ops: Vec<MachineOperand>, def: u32) -> MachineInstr {
let mut inst = MachineInstr::new(opcode);
inst.operands = ops;
inst.def = Some(def);
inst
}
#[test]
fn test_add_x_plus_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::ADD as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert!(!matches!(r, FoldResult::Constant(_)));
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_add_constants() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::ADD as u32, vec![imm(3), imm(5)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(8));
}
#[test]
fn test_mul_by_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::MUL as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.integer_zero, 1);
}
#[test]
fn test_mul_by_1() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::MUL as u32, vec![reg(1), imm(1)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_and_minus_1() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::AND as u32, vec![reg(1), imm(-1)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_and_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::AND as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
}
#[test]
fn test_or_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::OR as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_or_all_ones() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::OR as u32, vec![reg(1), imm(-1)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(-1));
}
#[test]
fn test_xor_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::XOR as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_xor_self() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::XOR as u32, vec![reg(1), reg(1)], 3);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.self_cancel, 1);
}
#[test]
fn test_sub_self() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SUB as u32, vec![reg(1), reg(1)], 3);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.self_cancel, 1);
}
#[test]
fn test_sub_const() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SUB as u32, vec![imm(10), imm(3)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(7));
}
#[test]
fn test_neg_zero() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::NEG as u32, vec![imm(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
}
#[test]
fn test_neg_const() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::NEG as u32, vec![imm(42)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(-42));
}
#[test]
fn test_not_const() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::NOT as u32, vec![imm(0xF0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(!0xF0));
}
#[test]
fn test_udiv_by_1() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::DIV as u32, vec![reg(1), imm(1)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_udiv_by_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::DIV as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Undefined);
}
#[test]
fn test_udiv_0_by_x() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::DIV as u32, vec![imm(0), reg(1)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
}
#[test]
fn test_sdiv_neg1() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::IDIV as u32, vec![imm(10), imm(-1)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(-10));
}
#[test]
fn test_sdiv_min_by_neg1() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::IDIV as u32, vec![imm(i64::MIN), imm(-1)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Poison);
}
#[test]
fn test_shl_0_src() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SHL as u32, vec![imm(0), reg(2)], 3);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
}
#[test]
fn test_shl_identity() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SHL as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.shift_identity, 1);
}
#[test]
fn test_shl_const() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SHL as u32, vec![imm(1), imm(3)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(8));
}
#[test]
fn test_lshr_0_src() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SHR as u32, vec![imm(0), reg(2)], 3);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
}
#[test]
fn test_ashr_0_src() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SAR as u32, vec![imm(0), reg(2)], 3);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
}
#[test]
fn test_rol_identity() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::ROL as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.shift_identity, 1);
}
#[test]
fn test_ror_identity() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::ROR as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.shift_identity, 1);
}
#[test]
fn test_select_true() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_select(&imm(1), &imm(42), &imm(99)),
Some(imm(42))
);
assert_eq!(s.stats.select_folded, 1);
}
#[test]
fn test_select_false() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_select(&imm(0), &imm(42), &imm(99)),
Some(imm(99))
);
assert_eq!(s.stats.select_folded, 1);
}
#[test]
fn test_select_unknown() {
let mut s = X86InstSimplify::new();
assert_eq!(s.simplify_select(®(5), &imm(42), &imm(99)), None);
assert_eq!(s.stats.select_folded, 0);
}
#[test]
fn test_select_same_alternatives() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_select_same_alternatives(®(1), &imm(42), &imm(42)),
Some(imm(42))
);
assert_eq!(s.stats.select_folded, 1);
}
#[test]
fn test_icmp_eq_same() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_icmp_same_ops(0, ®(1), ®(1)),
FoldResult::Constant(1)
);
}
#[test]
fn test_icmp_ne_same() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_icmp_same_ops(1, ®(1), ®(1)),
FoldResult::Constant(0)
);
}
#[test]
fn test_icmp_slt_same() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_icmp_same_ops(8, ®(1), ®(1)),
FoldResult::Constant(0)
);
}
#[test]
fn test_icmp_sle_same() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_icmp_same_ops(9, ®(1), ®(1)),
FoldResult::Constant(1)
);
}
#[test]
fn test_icmp_const() {
let mut s = X86InstSimplify::new();
assert_eq!(s.simplify_icmp_const(0, 5, 5), FoldResult::Constant(1));
assert_eq!(s.simplify_icmp_const(6, 5, 3), FoldResult::Constant(1)); assert_eq!(s.simplify_icmp_const(8, 5, 3), FoldResult::Constant(0)); }
#[test]
fn test_phi_all_same() {
let mut s = X86InstSimplify::new();
assert_eq!(
s.simplify_phi_same_values(&[imm(42), imm(42), imm(42)]),
Some(imm(42))
);
assert_eq!(s.stats.phi_folded, 1);
}
#[test]
fn test_phi_different() {
let mut s = X86InstSimplify::new();
assert_eq!(s.simplify_phi_same_values(&[imm(42), imm(43)]), None);
assert_eq!(s.stats.phi_folded, 0);
}
#[test]
fn test_phi_empty() {
let mut s = X86InstSimplify::new();
assert_eq!(s.simplify_phi_same_values(&[]), None);
}
#[test]
fn test_gep_all_zero() {
let mut s = X86InstSimplify::new();
assert!(s.simplify_gep_zero_indices(&[reg(0), imm(0), imm(0), imm(0)]));
assert_eq!(s.stats.gep_folded, 1);
}
#[test]
fn test_gep_nonzero() {
let mut s = X86InstSimplify::new();
assert!(!s.simplify_gep_zero_indices(&[reg(0), imm(0), imm(8)]));
assert_eq!(s.stats.gep_folded, 0);
}
#[test]
fn test_cast_same_width() {
let mut s = X86InstSimplify::new();
let result = s.simplify_cast_identity(®(1), 32, 32);
assert_eq!(result, Some(reg(1)));
assert_eq!(s.stats.cast_identity, 1);
}
#[test]
fn test_cast_different_width() {
let mut s = X86InstSimplify::new();
let result = s.simplify_cast_identity(®(1), 32, 64);
assert_eq!(result, None);
}
#[test]
fn test_is_fp_nan() {
assert!(is_fp_nan_bits(0x7FF8000000000001));
assert!(!is_fp_nan_bits(0x7FF0000000000000)); assert!(!is_fp_nan_bits(0x3FF0000000000000)); }
#[test]
fn test_is_fp_inf() {
assert!(is_fp_inf_bits(0x7FF0000000000000));
assert!(is_fp_neg_inf_bits(0xFFF0000000000000));
assert!(!is_fp_inf_bits(0x3FF0000000000000));
}
#[test]
fn test_fmul_1_double() {
let mut s = X86InstSimplify::new();
let one_bits = 0x3FF0000000000000u64 as i64;
let inst = make_inst(X86Opcode::MULSD as u32, vec![reg(1), imm(one_bits)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fdiv_1_double() {
let mut s = X86InstSimplify::new();
let one_bits = 0x3FF0000000000000u64 as i64;
let inst = make_inst(X86Opcode::DIVSD as u32, vec![reg(1), imm(one_bits)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fadd_neg_zero_double() {
let mut s = X86InstSimplify::new();
let neg_zero = 0x8000000000000000u64 as i64;
let inst = make_inst(X86Opcode::ADDSD as u32, vec![reg(1), imm(neg_zero)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fdiv_by_zero() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::DIVSD as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF0000000000000));
assert_eq!(s.stats.fp_infinity, 1);
}
#[test]
fn test_fsqrt_zero() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SQRTSD as u32, vec![imm(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::FpConstant(0));
}
#[test]
fn test_fsqrt_one() {
let mut s = X86InstSimplify::new();
let one_bits = 0x3FF0000000000000u64 as i64;
let inst = make_inst(X86Opcode::SQRTSD as u32, vec![imm(one_bits)], 1);
let info = X86InstrInfo::new();
assert_eq!(
s.simplify(&inst, &info),
FoldResult::FpConstant(0x3FF0000000000000)
);
}
#[test]
fn test_fma_with_0_accumulator() {
let mut s = X86InstSimplify::new();
let inst = make_inst(
X86Opcode::VFMADD213PD as u32,
vec![reg(0), reg(1), imm(0)],
3,
);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_vector_xor_self() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VXORPS as u32, vec![reg(0), reg(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.vector_self_cancel, 1);
}
#[test]
fn test_vector_and_all_ones() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VANDPD as u32, vec![reg(0), imm(-1)], 1);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_all_ones, 1);
}
#[test]
fn test_vector_and_zero() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VANDPS as u32, vec![reg(0), imm(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.vector_zero, 1);
}
#[test]
fn test_vector_or_zero() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VORPS as u32, vec![reg(0), imm(0)], 1);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_identity, 1);
}
#[test]
fn test_vector_andn_zero() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::ANDNPS as u32, vec![reg(0), imm(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.vector_zero, 1);
}
#[test]
fn test_shuffle_identity_pshufd() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::PSHUFD as u32, vec![reg(0), imm(0xE4)], 1);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_identity, 1);
}
#[test]
fn test_simplify_block_empty() {
let mut s = X86InstSimplify::new();
let mut block = MachineBasicBlock {
name: "test".into(),
instructions: vec![],
successors: vec![],
};
let info = X86InstrInfo::new();
assert!(!s.simplify_block(&mut block, &info));
}
#[test]
fn test_clear_resets() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::ADD as u32, vec![imm(1), imm(2)], 3);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
s.clear();
assert_eq!(s.stats.total_simplified, 0);
assert_eq!(s.stats.integer_identity, 0);
}
#[test]
fn test_stats_merge() {
let mut a = SimplifyStats::default();
a.integer_identity = 5;
a.integer_zero = 3;
let b = SimplifyStats {
integer_identity: 2,
integer_zero: 1,
..Default::default()
};
a.merge(&b);
assert_eq!(a.integer_identity, 7);
assert_eq!(a.integer_zero, 4);
}
#[test]
fn test_fold_result_is_simplified() {
assert!(FoldResult::Constant(0).is_simplified());
assert!(FoldResult::Undefined.is_simplified());
assert!(FoldResult::Poison.is_simplified());
assert!(!FoldResult::NotSimplified.is_simplified());
}
#[test]
fn test_fold_result_display() {
assert_eq!(format!("{}", FoldResult::Constant(5)), "Constant(5)");
assert_eq!(format!("{}", FoldResult::NotSimplified), "NotSimplified");
}
#[test]
fn test_make_inst_simplify_no_fp() {
let s = make_x86_inst_simplify_no_fp();
assert!(!s.fold_fp_identities);
assert!(!s.simplify_fp_nan);
}
#[test]
fn test_udiv_const() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::DIV as u32, vec![imm(10), imm(3)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::ConstantU(3));
}
#[test]
fn test_sdiv_const() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::IDIV as u32, vec![imm(10), imm(3)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(3));
}
#[test]
fn test_simplify_block_multiple_insts() {
let mut s = X86InstSimplify::new();
let info = X86InstrInfo::new();
let mut block = MachineBasicBlock {
name: "test".into(),
instructions: vec![
make_inst(X86Opcode::ADD as u32, vec![imm(2), imm(3)], 1),
make_inst(X86Opcode::SUB as u32, vec![imm(10), imm(5)], 2),
make_inst(X86Opcode::MUL as u32, vec![imm(6), imm(7)], 3),
],
successors: vec![],
};
assert!(s.simplify_block(&mut block, &info));
assert_eq!(s.stats.total_simplified, 3);
}
#[test]
fn test_simplify_function() {
let mut s = X86InstSimplify::new();
let info = X86InstrInfo::new();
let mut blocks = vec![MachineBasicBlock {
name: "entry".into(),
instructions: vec![make_inst(X86Opcode::XOR as u32, vec![reg(1), reg(1)], 1)],
successors: vec![],
}];
let count = s.simplify_function(&mut blocks, &info);
assert_eq!(count, 1);
}
#[test]
fn test_icmp_all_predicates_same_ops() {
let mut s = X86InstSimplify::new();
let expected = [
(0, 1), (1, 0), (2, 0), (3, 1), (4, 0), (5, 1), (6, 0), (7, 1), (8, 0), (9, 1), ];
for (pred, expected_val) in &expected {
let r = s.simplify_icmp_same_ops(*pred, ®(1), ®(1));
assert_eq!(
r,
FoldResult::Constant(*expected_val as i64),
"pred={}",
pred
);
}
assert_eq!(s.stats.icmp_folded, 10);
}
#[test]
fn test_icmp_const_all_predicates() {
let mut s = X86InstSimplify::new();
assert_eq!(s.simplify_icmp_const(0, 5, 3), FoldResult::Constant(0)); assert_eq!(s.simplify_icmp_const(1, 5, 3), FoldResult::Constant(1)); assert_eq!(s.simplify_icmp_const(6, 5, 3), FoldResult::Constant(1)); assert_eq!(s.simplify_icmp_const(8, 5, 3), FoldResult::Constant(0)); assert_eq!(s.simplify_icmp_const(7, 5, 3), FoldResult::Constant(1)); assert_eq!(s.simplify_icmp_const(9, 5, 3), FoldResult::Constant(0)); }
#[test]
fn test_gep_const_sum() {
let mut s = X86InstSimplify::new();
let r = s.simplify_gep_const(100, &[1, 2, 3]);
assert_eq!(r, FoldResult::Constant(6));
}
#[test]
fn test_extractvalue_const() {
let mut s = X86InstSimplify::new();
let r = s.simplify_extractvalue_const(&[10, 20, 30], 1);
assert!(r.is_some());
assert_eq!(s.stats.extract_insert_folded, 1);
}
#[test]
fn test_fma_with_nan() {
let mut s = X86InstSimplify::new();
let nan_bits = 0x7FF8000000000001u64 as i64;
let inst = make_inst(
X86Opcode::VFMADD213PD as u32,
vec![reg(0), reg(1), imm(nan_bits)],
3,
);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF8000000000000));
assert_eq!(s.stats.fp_nan, 1);
}
#[test]
fn test_fma_disabled_fp_identity() {
let mut s = X86InstSimplify::new();
s.fold_fp_identities = false;
let inst = make_inst(
X86Opcode::VFMADD213PD as u32,
vec![reg(0), reg(1), imm(0)],
3,
);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::NotSimplified);
}
#[test]
fn test_simplify_adc_identity() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::ADC as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_simplify_sbb_identity() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SBB as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.integer_identity, 1);
}
#[test]
fn test_fadd_neg_zero_single() {
let mut s = X86InstSimplify::new();
let neg_zero = 0x80000000u32 as i64;
let inst = make_inst(X86Opcode::ADDSS as u32, vec![reg(1), imm(neg_zero)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fsub_neg_zero_single() {
let mut s = X86InstSimplify::new();
let neg_zero = 0x80000000u32 as i64;
let inst = make_inst(X86Opcode::SUBSS as u32, vec![reg(1), imm(neg_zero)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fmul_1_single() {
let mut s = X86InstSimplify::new();
let one_bits = 0x3F800000u32 as i64;
let inst = make_inst(X86Opcode::MULSS as u32, vec![reg(1), imm(one_bits)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fdiv_1_single() {
let mut s = X86InstSimplify::new();
let one_bits = 0x3F800000u32 as i64;
let inst = make_inst(X86Opcode::DIVSS as u32, vec![reg(1), imm(one_bits)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fmin_same_reg() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::MINSS as u32, vec![reg(1), reg(1)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::NotSimplified);
}
#[test]
fn test_fmax_same_reg() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::MAXSD as u32, vec![reg(1), reg(1)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
}
#[test]
fn test_fmin_with_nan() {
let mut s = X86InstSimplify::new();
let nan_bits = 0x7FF8000000000001u64 as i64;
let inst = make_inst(X86Opcode::MINSD as u32, vec![reg(1), imm(nan_bits)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF8000000000000));
assert_eq!(s.stats.fp_nan, 1);
}
#[test]
fn test_fmax_with_nan() {
let mut s = X86InstSimplify::new();
let nan_bits = 0x7FF8000000000001u64 as i64;
let inst = make_inst(X86Opcode::MAXSD as u32, vec![reg(1), imm(nan_bits)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF8000000000000));
}
#[test]
fn test_simplify_shl_const_large() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SHL as u32, vec![imm(1), imm(100)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert!(r.is_constant());
}
#[test]
fn test_lshr_const() {
let mut s = X86InstSimplify::new();
let inst = make_inst(
X86Opcode::SHR as u32,
vec![imm(0x8000000000000000u64 as i64), imm(63)],
1,
);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::Constant(1));
}
#[test]
fn test_ashr_const_negative() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SAR as u32, vec![imm(-8), imm(1)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(-4));
}
#[test]
fn test_vector_sub_self() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VSUBPS as u32, vec![reg(0), reg(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.vector_self_cancel, 1);
}
#[test]
fn test_vector_div_self() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VDIVPS as u32, vec![reg(0), reg(0)], 1);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_identity, 1);
}
#[test]
fn test_vector_or_all_ones() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VORPD as u32, vec![reg(0), imm(-1)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(-1));
assert_eq!(s.stats.vector_all_ones, 1);
}
#[test]
fn test_vector_andn_all_ones() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VANDNPS as u32, vec![reg(0), imm(-1)], 1);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_all_ones, 1);
}
#[test]
fn test_vector_int_add_zero() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VPADDD_Z as u32, vec![reg(0), reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_identity, 1);
}
#[test]
fn test_vector_int_sub_self() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VPSUBD_Z as u32, vec![reg(0), reg(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
assert_eq!(s.stats.vector_self_cancel, 1);
}
#[test]
fn test_vector_int_mul_one() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::VPMULLD_Z as u32, vec![reg(0), reg(1), imm(1)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_identity, 1);
}
#[test]
fn test_horizontal_same_operands() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::HADDPS as u32, vec![reg(0), reg(0)], 1);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_identity, 1);
}
#[test]
fn test_shuffle_non_identity() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::PSHUFD as u32, vec![reg(0), imm(0x1B)], 1);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.vector_identity, 0);
}
#[test]
fn test_simplify_block_with_undef() {
let mut s = X86InstSimplify::new();
let info = X86InstrInfo::new();
let mut block = MachineBasicBlock {
name: "test".into(),
instructions: vec![make_inst(
X86Opcode::ADD as u32,
vec![reg(u32::MAX), reg(1)],
2,
)],
successors: vec![],
};
assert!(s.simplify_block(&mut block, &info));
assert_eq!(s.stats.undef_propagated, 1);
}
#[test]
fn test_stats_summary_format() {
let s = X86InstSimplify::new();
let summary = s.stats_summary();
assert!(summary.contains("X86InstSimplify"));
assert!(summary.contains("total=0"));
}
#[test]
fn test_fold_result_as_u64() {
assert_eq!(FoldResult::ConstantU(42).as_u64(), Some(42));
assert_eq!(FoldResult::FpConstant(0x3FF).as_u64(), Some(0x3FF));
assert_eq!(FoldResult::Constant(5).as_u64(), Some(5));
assert_eq!(FoldResult::Constant(-1).as_u64(), None);
assert_eq!(FoldResult::NotSimplified.as_u64(), None);
}
#[test]
fn test_fold_result_as_i64() {
assert_eq!(FoldResult::Constant(42).as_i64(), Some(42));
assert_eq!(FoldResult::ConstantU(42).as_i64(), None);
assert_eq!(FoldResult::NotSimplified.as_i64(), None);
}
#[test]
fn test_udiv_const_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::DIV as u32, vec![imm(10), imm(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Undefined);
}
#[test]
fn test_sdiv_const_0() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::IDIV as u32, vec![imm(10), imm(0)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Undefined);
}
#[test]
fn test_max_simplifications_per_block() {
let mut s = X86InstSimplify::new();
s.max_simplifications_per_block = 2;
let info = X86InstrInfo::new();
let mut block = MachineBasicBlock {
name: "test".into(),
instructions: vec![
make_inst(X86Opcode::ADD as u32, vec![imm(1), imm(2)], 1),
make_inst(X86Opcode::ADD as u32, vec![imm(3), imm(4)], 2),
make_inst(X86Opcode::ADD as u32, vec![imm(5), imm(6)], 3),
make_inst(X86Opcode::ADD as u32, vec![imm(7), imm(8)], 4),
],
successors: vec![],
};
let changed = s.simplify_block(&mut block, &info);
assert!(changed);
assert!(s.stats.total_simplified <= 2);
}
}
#[test]
fn test_fp_nan_simplification_disabled() {
let mut s = X86InstSimplify::new();
s.simplify_fp_nan = false;
let nan_bits = 0x7FF0000000000001u64 as i64;
let inst = make_inst(X86Opcode::ADDSD as u32, vec![reg(1), imm(nan_bits)], 2);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::NotSimplified);
assert_eq!(s.stats.fp_nan, 0);
}
#[test]
fn test_fmul_1_double_commutative() {
let mut s = X86InstSimplify::new();
let one_bits = 0x3FF0000000000000u64 as i64;
let inst = make_inst(X86Opcode::MULSD as u32, vec![imm(one_bits), reg(1)], 2);
let info = X86InstrInfo::new();
s.simplify(&inst, &info);
assert_eq!(s.stats.fp_identity, 1);
}
#[test]
fn test_fmul_0_double() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::MULSD as u32, vec![reg(1), imm(0)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0));
}
#[test]
fn test_fsqrt_nan() {
let mut s = X86InstSimplify::new();
let nan_bits = 0x7FF8000000000001u64 as i64;
let inst = make_inst(X86Opcode::SQRTSD as u32, vec![imm(nan_bits)], 1);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF8000000000000));
}
#[test]
fn test_fadd_nan_first_operand() {
let mut s = X86InstSimplify::new();
let nan_bits = 0x7FF8000000000001u64 as i64;
let inst = make_inst(X86Opcode::ADDSD as u32, vec![imm(nan_bits), reg(1)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF8000000000000));
}
#[test]
fn test_fsub_nan() {
let mut s = X86InstSimplify::new();
let nan_bits = 0x7FF8000000000001u64 as i64;
let inst = make_inst(X86Opcode::SUBSD as u32, vec![imm(nan_bits), reg(1)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF8000000000000));
}
#[test]
fn test_fdiv_nan() {
let mut s = X86InstSimplify::new();
let nan_bits = 0x7FF8000000000001u64 as i64;
let inst = make_inst(X86Opcode::DIVSD as u32, vec![imm(nan_bits), reg(1)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::FpConstant(0x7FF8000000000000));
}
#[test]
fn test_fadd_inf_simplify() {
let mut s = X86InstSimplify::new();
let inf_bits = 0x7FF0000000000000u64 as i64;
let inst = make_inst(X86Opcode::ADDSD as u32, vec![imm(inf_bits), reg(1)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::NotSimplified);
}
#[test]
fn test_is_fp_neg_zero_f64() {
assert!(!is_fp_pos_zero(0x8000000000000000));
assert!(is_fp_neg_zero(0x8000000000000000));
assert!(!is_fp_neg_zero(0x0000000000000000));
assert!(is_fp_pos_zero(0x0000000000000000));
}
#[test]
fn test_is_fp32_neg_zero() {
assert!(is_fp32_neg_zero(0x80000000));
assert!(!is_fp32_neg_zero(0x00000000));
}
#[test]
fn test_undef_propagation_disabled() {
let mut s = X86InstSimplify::new();
s.propagate_undef = false;
let inst = make_inst(X86Opcode::ADD as u32, vec![reg(u32::MAX), reg(1)], 2);
let info = X86InstrInfo::new();
let r = s.simplify(&inst, &info);
assert_eq!(r, FoldResult::NotSimplified);
assert_eq!(s.stats.undef_propagated, 0);
}
#[test]
fn test_sub_0_minus_x() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SUB as u32, vec![imm(0), imm(5)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(-5));
}
#[test]
fn test_sub_x_minus_x_regs() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::SUB as u32, vec![reg(3), reg(3)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(0));
}
#[test]
fn test_sdiv_neg1_with_overflow_protection() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::IDIV as u32, vec![imm(i64::MIN), imm(-1)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Poison);
}
#[test]
fn test_sdiv_normal() {
let mut s = X86InstSimplify::new();
let inst = make_inst(X86Opcode::IDIV as u32, vec![imm(100), imm(3)], 1);
let info = X86InstrInfo::new();
assert_eq!(s.simplify(&inst, &info), FoldResult::Constant(33));
}
#[test]
fn test_select_nonzero_condition_is_true() {
let mut s = X86InstSimplify::new();
let result = s.simplify_select(&imm(42), &imm(100), &imm(200));
assert_eq!(result, Some(imm(100)));
assert_eq!(s.stats.select_folded, 1);
}
#[test]
fn test_select_neg1_condition_is_true() {
let mut s = X86InstSimplify::new();
let result = s.simplify_select(&imm(-1), &imm(100), &imm(200));
assert_eq!(result, Some(imm(100)));
}
#[test]
fn test_is_double_detection() {
assert!(is_double(X86Opcode::ADDSD as u32));
assert!(is_double(X86Opcode::MULSD as u32));
assert!(!is_double(X86Opcode::ADDSS as u32));
assert!(!is_double(X86Opcode::MULSS as u32));
}
#[test]
fn test_classify_operand_imm() {
assert_eq!(classify_operand(&imm(5)), OperandValue::Const(5));
assert_eq!(classify_operand(&imm(-3)), OperandValue::Const(-3));
}
#[test]
fn test_classify_operand_reg() {
assert_eq!(classify_operand(®(5)), OperandValue::Unknown);
}
#[test]
fn test_classify_operand_undef() {
assert_eq!(classify_operand(®(u32::MAX)), OperandValue::Undef);
}
#[test]
fn test_is_undef_helper() {
assert!(is_undef(®(u32::MAX)));
assert!(!is_undef(®(1)));
assert!(!is_undef(&imm(0)));
}
#[test]
fn test_is_constant_helper() {
assert!(is_constant(&imm(0)));
assert!(is_constant(&imm(42)));
assert!(!is_constant(®(5)));
}
#[test]
fn test_is_poison_helper() {
assert!(!is_poison(&imm(0)));
assert!(!is_poison(®(1)));
}
#[test]
fn test_is_zero_helper() {
assert!(is_zero(&imm(0)));
assert!(!is_zero(&imm(1)));
assert!(!is_zero(®(0)));
}
#[test]
fn test_is_one_helper() {
assert!(is_one(&imm(1)));
assert!(!is_one(&imm(2)));
}
#[test]
fn test_is_all_ones_helper() {
assert!(is_all_ones(&imm(-1)));
assert!(!is_all_ones(&imm(0)));
}
#[test]
fn test_is_neg_one_helper() {
assert!(is_neg_one(&imm(-1)));
assert!(!is_neg_one(&imm(0)));
}
#[test]
fn test_fold_result_is_constant() {
assert!(FoldResult::Constant(0).is_constant());
assert!(FoldResult::ConstantU(0).is_constant());
assert!(FoldResult::FpConstant(0).is_constant());
assert!(!FoldResult::Poison.is_constant());
assert!(!FoldResult::Undefined.is_constant());
assert!(!FoldResult::NotSimplified.is_constant());
}