use crate::opcode::{ICmpPred, Opcode};
use crate::types::Type;
use crate::value::{valref, Value, ValueRef};
use crate::SubclassKind;
use std::collections::{HashMap, HashSet, VecDeque};
fn get_constant_i64(val: &ValueRef) -> Option<i64> {
let v = val.borrow();
if v.is_constant() {
if let Ok(n) = v.name.parse::<i64>() {
return Some(n);
}
match v.name.as_str() {
"true" => return Some(1),
"false" => return Some(0),
"null" => return Some(0),
"zeroinitializer" => return Some(0),
_ => {}
}
if v.subclass_data != 0 || v.name == "0" {
return Some(v.subclass_data as i64);
}
}
None
}
fn get_constant_u64(val: &ValueRef) -> Option<u64> {
get_constant_i64(val).map(|i| i as u64)
}
fn get_opcode(val: &ValueRef) -> Option<Opcode> {
val.borrow().opcode
}
fn has_opcode(val: &ValueRef, op: Opcode) -> bool {
val.borrow().opcode == Some(op)
}
fn get_subclass(val: &ValueRef) -> SubclassKind {
val.borrow().subclass
}
fn get_type(val: &ValueRef) -> Type {
val.borrow().ty.clone()
}
fn get_operand(val: &ValueRef, idx: usize) -> Option<ValueRef> {
val.borrow().operands.get(idx).cloned()
}
fn num_operands(val: &ValueRef) -> usize {
val.borrow().operands.len()
}
fn type_bit_width(ty: &Type) -> u32 {
match ty {
Type::Int(w) => *w,
Type::Int1 => 1,
Type::Int8 => 8,
Type::Int16 => 16,
Type::Int32 => 32,
Type::Int64 => 64,
Type::Int128 => 128,
Type::Float => 32,
Type::Double => 64,
Type::FP128 => 128,
Type::Pointer(_inner) => 64, _ => 64, }
}
fn value_bit_width(val: &ValueRef) -> u32 {
let ty = get_type(val);
type_bit_width(&ty)
}
const MAX_TRACKED_BITS: u32 = 128;
fn make_dummy_ref(vid: u64, opcode: Opcode) -> ValueRef {
let mut v = Value::new(Type::Int32);
v.vid = vid;
v.opcode = Some(opcode);
v.subclass = SubclassKind::Instruction;
valref(v)
}
fn make_const_i32(vid: u64, val: i32, cname: &str) -> ValueRef {
let mut v = Value::new(Type::Int32);
v.vid = vid;
v.subclass = SubclassKind::ConstantInt;
v.name = if cname.is_empty() {
val.to_string()
} else {
cname.to_string()
};
v.subclass_data = val as u32;
valref(v)
}
fn make_const_i64(vid: u64, val: i64) -> ValueRef {
let mut v = Value::new(Type::Int64);
v.vid = vid;
v.subclass = SubclassKind::ConstantInt;
v.name = val.to_string();
valref(v)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct X86KnownBits {
pub zero: u128,
pub one: u128,
}
impl X86KnownBits {
pub fn unknown() -> Self {
Self { zero: 0, one: 0 }
}
pub fn constant(value: u128) -> Self {
Self {
zero: !value,
one: value,
}
}
pub fn new(zero: u128, one: u128) -> Self {
let conflict = zero & one;
Self {
zero: zero & !conflict,
one: one & !conflict,
}
}
pub fn bounds(max_value: u128) -> Self {
if max_value == 0 {
return Self { zero: !0, one: 0 };
}
let leading_zeros = max_value.leading_zeros();
let high_zeros = if leading_zeros < 128 {
!0u128 << (128 - leading_zeros)
} else {
0
};
Self {
zero: high_zeros,
one: 0,
}
}
pub fn range(lo: u128, hi: u128) -> Self {
if lo == hi {
return Self::constant(lo);
}
let xor_lo_hi = lo ^ hi;
let common_prefix_len = xor_lo_hi.leading_zeros();
let prefix_mask = if common_prefix_len < 128 {
!0u128 << (128 - common_prefix_len)
} else {
!0u128
};
let known_one = lo & prefix_mask;
let known_zero = (!lo) & prefix_mask;
Self {
zero: known_zero,
one: known_one,
}
}
pub fn known_mask(&self) -> u128 {
self.zero | self.one
}
pub fn is_constant(&self) -> bool {
self.known_mask() == !0u128
}
pub fn get_constant(&self) -> Option<u128> {
if self.is_constant() {
Some(self.one)
} else {
None
}
}
pub fn has_known_bits(&self) -> bool {
self.zero != 0 || self.one != 0
}
pub fn is_zero(&self) -> bool {
self.is_constant() && self.one == 0
}
pub fn is_all_ones(&self) -> bool {
self.is_constant() && self.one == !0u128
}
pub fn is_known_negative(&self) -> bool {
(self.one >> 127) & 1 == 1
}
pub fn is_known_non_negative(&self) -> bool {
(self.zero >> 127) & 1 == 1
}
pub fn is_known_power_of_two(&self) -> bool {
self.is_constant() && self.one.count_ones() == 1
}
pub fn compute_and(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
X86KnownBits {
zero: a.zero | b.zero,
one: a.one & b.one,
}
}
pub fn compute_or(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
X86KnownBits {
zero: a.zero & b.zero,
one: a.one | b.one,
}
}
pub fn compute_xor(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
let known_and_equal = (a.zero & b.zero) | (a.one & b.one);
let known_one = (a.one & b.zero) | (a.zero & b.one);
X86KnownBits {
zero: known_and_equal & !known_one,
one: known_one,
}
}
pub fn compute_add(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
let mut result = X86KnownBits::unknown();
let a_lsb0 = (a.zero & 1) == 1;
let b_lsb0 = (b.zero & 1) == 1;
let a_lsb1 = (a.one & 1) == 1;
let b_lsb1 = (b.one & 1) == 1;
if (a_lsb0 && b_lsb0) || (a_lsb1 && b_lsb1) {
result.zero |= 1;
}
if (a_lsb0 && b_lsb1) || (a_lsb1 && b_lsb0) {
result.one |= 1;
}
let mut carry_in: i8 = 0; for i in 0..128 {
let mask = 1u128 << i;
let a0 = (a.zero & mask) != 0;
let b0 = (b.zero & mask) != 0;
let a1 = (a.one & mask) != 0;
let b1 = (b.one & mask) != 0;
if !a0 && !a1 && !b0 && !b1 {
carry_in = 2;
break;
}
if a0 && b0 && carry_in == 0 {
result.zero |= mask;
carry_in = 0;
} else if a1 && b1 && carry_in == 0 {
result.zero |= mask;
carry_in = 1;
} else if a0 && b0 && carry_in == 1 {
result.one |= mask;
carry_in = 0;
} else if a1 && b1 && carry_in == 1 {
result.one |= mask;
carry_in = 1;
} else if ((a0 && b1) || (a1 && b0)) && carry_in == 0 {
result.one |= mask;
carry_in = 0;
} else if ((a0 && b1) || (a1 && b0)) && carry_in == 1 {
result.zero |= mask;
carry_in = 1;
} else {
carry_in = 2;
break;
}
}
result
}
pub fn compute_sub(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
X86KnownBits::compute_add(a, &X86KnownBits::compute_not(b))
}
pub fn compute_mul(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
if a.is_zero() || b.is_zero() {
return X86KnownBits::constant(0);
}
if a.is_constant() && a.one == 1 {
return *b;
}
if b.is_constant() && b.one == 1 {
return *a;
}
if let Some(ca) = a.get_constant() {
if let Some(cb) = b.get_constant() {
return X86KnownBits::constant(ca.wrapping_mul(cb));
}
}
let tz = (a.zero.trailing_ones().min(b.zero.trailing_ones())).min(128);
let zero = if tz > 0 { (1u128 << tz) - 1 } else { 0 };
X86KnownBits { zero, one: 0 }
}
pub fn compute_shl(a: &X86KnownBits, shift: u32, bit_width: u32) -> X86KnownBits {
if shift >= bit_width {
return X86KnownBits {
zero: !0u128,
one: 0,
};
}
let mask = if bit_width < 128 {
(1u128 << bit_width) - 1
} else {
!0u128
};
X86KnownBits {
zero: ((a.zero << shift) | ((1u128 << shift) - 1)) & mask,
one: (a.one << shift) & mask,
}
}
pub fn compute_lshr(a: &X86KnownBits, shift: u32, bit_width: u32) -> X86KnownBits {
if shift >= bit_width {
return X86KnownBits {
zero: !0u128,
one: 0,
};
}
let high_mask = if bit_width < 128 {
!0u128 ^ ((1u128 << (bit_width - shift)) - 1)
} else {
!0u128 ^ ((1u128 << (128 - shift)) - 1)
};
X86KnownBits {
zero: (a.zero >> shift) | high_mask,
one: a.one >> shift,
}
}
pub fn compute_ashr(a: &X86KnownBits, shift: u32, bit_width: u32) -> X86KnownBits {
if shift >= bit_width {
let sign = (a.one >> (bit_width - 1)) & 1;
return if sign != 0 {
X86KnownBits::constant(!0u128)
} else {
X86KnownBits::constant(0)
};
}
let sign_bit = 1u128 << (bit_width - 1);
let sign_one = (a.one & sign_bit) != 0;
let sign_ext = if sign_one {
!0u128 << (bit_width - shift)
} else {
0
};
X86KnownBits {
zero: (a.zero >> shift) | (!sign_ext & (!0u128 << (bit_width - shift))),
one: (a.one >> shift) | sign_ext,
}
}
pub fn compute_not(a: &X86KnownBits) -> X86KnownBits {
X86KnownBits {
zero: a.one,
one: a.zero,
}
}
pub fn compute_select(
cond: &X86KnownBits,
true_val: &X86KnownBits,
false_val: &X86KnownBits,
) -> X86KnownBits {
if cond.is_zero() {
return *false_val;
}
if cond.is_all_ones() || (cond.is_known_non_negative() && !cond.is_zero()) {
return *true_val;
}
X86KnownBits::intersect(true_val, false_val)
}
pub fn compute_trunc(a: &X86KnownBits, dst_bit_width: u32) -> X86KnownBits {
if dst_bit_width >= 128 {
return *a;
}
let mask = (1u128 << dst_bit_width) - 1;
X86KnownBits {
zero: (a.zero & mask) | !mask,
one: a.one & mask,
}
}
pub fn compute_zext(a: &X86KnownBits, src_bit_width: u32) -> X86KnownBits {
if src_bit_width >= 128 {
return *a;
}
let mask = (1u128 << src_bit_width) - 1;
X86KnownBits {
zero: (a.zero & mask) | !mask,
one: a.one & mask,
}
}
pub fn compute_sext(a: &X86KnownBits, src_bit_width: u32) -> X86KnownBits {
if src_bit_width >= 128 {
return *a;
}
let mask = (1u128 << src_bit_width) - 1;
let sign_bit = 1u128 << (src_bit_width - 1);
let sign_one = (a.one & sign_bit) != 0;
let ext = if sign_one { !0u128 << src_bit_width } else { 0 };
X86KnownBits {
zero: ((a.zero & mask) | !mask) & !ext,
one: (a.one & mask) | ext,
}
}
pub fn compute_icmp(pred: ICmpPred, a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
let a_const = a.get_constant();
let b_const = b.get_constant();
match (a_const, b_const) {
(Some(av), Some(bv)) => {
let r = match pred {
ICmpPred::Eq => av == bv,
ICmpPred::Ne => av != bv,
ICmpPred::Ugt => av > bv,
ICmpPred::Uge => av >= bv,
ICmpPred::Ult => av < bv,
ICmpPred::Ule => av <= bv,
ICmpPred::Sgt => (av as i128) > (bv as i128),
ICmpPred::Sge => (av as i128) >= (bv as i128),
ICmpPred::Slt => (av as i128) < (bv as i128),
ICmpPred::Sle => (av as i128) <= (bv as i128),
};
if r {
X86KnownBits {
zero: !0u128 ^ 1,
one: 1,
}
} else {
X86KnownBits { zero: 1, one: 0 }
}
}
_ => {
let diff = (a.known_mask() & b.known_mask()) & (a.one ^ b.one);
match pred {
ICmpPred::Eq if diff != 0 => X86KnownBits { zero: 1, one: 0 },
ICmpPred::Ne if diff != 0 => X86KnownBits {
zero: !0u128 ^ 1,
one: 1,
},
_ => X86KnownBits::unknown(),
}
}
}
}
pub fn compute_fcmp(pred: u8, a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
if let (Some(av), Some(bv)) = (a.get_constant(), b.get_constant()) {
let af = f64::from_bits(av as u64);
let bf = f64::from_bits(bv as u64);
if af.is_nan() || bf.is_nan() {
return X86KnownBits { zero: 1, one: 0 };
}
let r = match pred {
1 => af == bf,
2 => af > bf,
3 => af >= bf,
4 => af < bf,
5 => af <= bf,
6 => af != bf,
14 => af != bf,
_ => false,
};
if r {
X86KnownBits {
zero: !0u128 ^ 1,
one: 1,
}
} else {
X86KnownBits { zero: 1, one: 0 }
}
} else {
X86KnownBits::unknown()
}
}
pub fn compute_phi(incoming: &[X86KnownBits]) -> X86KnownBits {
if incoming.is_empty() {
return X86KnownBits::unknown();
}
let mut r = incoming[0];
for i in 1..incoming.len() {
r = X86KnownBits::intersect(&r, &incoming[i]);
}
r
}
pub fn compute_call() -> X86KnownBits {
X86KnownBits::unknown()
}
pub fn compute_load() -> X86KnownBits {
X86KnownBits::unknown()
}
pub fn compute_freeze(a: &X86KnownBits) -> X86KnownBits {
*a
}
pub fn compute_udiv(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
if b.is_zero() {
return X86KnownBits::unknown();
}
if b.is_constant() && b.one == 1 {
return *a;
}
if let (Some(av), Some(bv)) = (a.get_constant(), b.get_constant()) {
if bv != 0 {
return X86KnownBits::constant(av / bv);
}
}
X86KnownBits::bounds(a.get_constant().unwrap_or(!0u128))
}
pub fn compute_sdiv(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
if b.is_zero() {
return X86KnownBits::unknown();
}
if b.is_constant() && b.one == 1 {
return *a;
}
if let (Some(av), Some(bv)) = (a.get_constant(), b.get_constant()) {
if bv != 0 {
return X86KnownBits::constant(((av as i128) / (bv as i128)) as u128);
}
}
X86KnownBits::unknown()
}
pub fn compute_urem(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
if b.is_zero() {
return X86KnownBits::unknown();
}
if b.is_constant() {
if b.one == 1 {
return X86KnownBits::constant(0);
}
return X86KnownBits::bounds(b.one - 1);
}
X86KnownBits::unknown()
}
pub fn compute_srem(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
if b.is_zero() {
return X86KnownBits::unknown();
}
if b.is_constant() && b.one == 1 {
return X86KnownBits::constant(0);
}
X86KnownBits::unknown()
}
pub fn intersect(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
X86KnownBits {
zero: a.zero & b.zero,
one: a.one & b.one,
}
}
pub fn union(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
X86KnownBits {
zero: a.zero & b.zero,
one: a.one & b.one,
}
}
pub fn widen(a: &X86KnownBits, b: &X86KnownBits) -> X86KnownBits {
X86KnownBits::intersect(a, b)
}
pub fn is_subset_of(&self, other: &X86KnownBits) -> bool {
(self.zero & !other.zero) == 0 && (self.one & !other.one) == 0
}
pub fn is_consistent_with(&self, other: &X86KnownBits) -> bool {
((self.zero & other.one) | (self.one & other.zero)) == 0
}
pub fn apply_mask(&self, mask: u128) -> X86KnownBits {
X86KnownBits {
zero: (self.zero & mask) | !mask,
one: self.one & mask,
}
}
pub fn trailing_known_zero_bits(&self) -> u32 {
let known = self.known_mask();
if known == 0 {
return 0;
}
let mut c = 0u32;
for i in 0..128 {
if (known & (1u128 << i)) != 0 && (self.zero & (1u128 << i)) != 0 {
c += 1;
} else {
break;
}
}
c
}
}
impl Default for X86KnownBits {
fn default() -> Self {
Self::unknown()
}
}
pub struct X86ComputeNumSignBits {
cache: HashMap<u64, u32>,
max_bit_width: u32,
}
impl X86ComputeNumSignBits {
pub fn new(max_bit_width: u32) -> Self {
Self {
cache: HashMap::new(),
max_bit_width,
}
}
pub fn default_for_x86_64() -> Self {
Self::new(64)
}
pub fn compute_num_sign_bits(&mut self, val: &ValueRef, vt: &X86ValueTracking) -> u32 {
let vid = val.borrow().vid;
if let Some(&cached) = self.cache.get(&vid) {
return cached;
}
let bw = value_bit_width(val).min(self.max_bit_width);
if bw == 0 {
return 0;
}
let result = self.compute_nsb_inner(val, vt, bw);
self.cache.insert(vid, result);
result
}
fn compute_nsb_inner(&self, val: &ValueRef, vt: &X86ValueTracking, bw: u32) -> u32 {
if let Some(c) = get_constant_u64(val) {
return self.count_sign_bits_u128(c as u128, bw);
}
let sub = get_subclass(val);
if sub == SubclassKind::Argument {
return 1;
}
match get_opcode(val) {
Some(Opcode::Add) | Some(Opcode::FAdd) => {
if let (Some(a), Some(b)) = (self.op_sb(val, 0, vt, bw), self.op_sb(val, 1, vt, bw))
{
let m = a.min(b);
if m > 1 {
m - 1
} else {
0
}
} else {
1
}
}
Some(Opcode::Sub) | Some(Opcode::FSub) => {
if let Some(a) = self.op_sb(val, 0, vt, bw) {
if a > 1 {
a - 1
} else {
0
}
} else {
0
}
}
Some(Opcode::Mul) | Some(Opcode::FMul) => {
if let (Some(a), Some(b)) = (self.op_sb(val, 0, vt, bw), self.op_sb(val, 1, vt, bw))
{
a.min(b)
} else {
0
}
}
Some(Opcode::And) | Some(Opcode::Or) | Some(Opcode::Xor) => {
if let (Some(a), Some(b)) = (self.op_sb(val, 0, vt, bw), self.op_sb(val, 1, vt, bw))
{
a.min(b)
} else {
1
}
}
Some(Opcode::Shl) => {
if let Some(a) = self.op_sb(val, 0, vt, bw) {
if let Some(sh) = self.const_op(val, 1) {
let s = (sh as u32).min(bw);
if s < a {
a - s
} else {
0
}
} else {
0
}
} else {
0
}
}
Some(Opcode::AShr) => {
if let Some(a) = self.op_sb(val, 0, vt, bw) {
if let Some(sh) = self.const_op(val, 1) {
(a + (sh as u32).min(bw)).min(bw)
} else {
a
}
} else {
1
}
}
Some(Opcode::LShr) => {
if let Some(sh) = self.const_op(val, 1) {
let s = (sh as u32).min(bw);
if let Some(a) = self.op_sb(val, 0, vt, bw) {
(s + if a > s { a - s } else { 0 }).min(bw)
} else {
s
}
} else {
1
}
}
Some(Opcode::Trunc) => {
if let Some(src) = get_operand(val, 0) {
let src_bw = value_bit_width(&src);
if let Some(a) = self.op_sb(val, 0, vt, bw) {
(a + src_bw.saturating_sub(bw)).min(bw)
} else {
1
}
} else {
1
}
}
Some(Opcode::ZExt) => {
if let Some(src) = get_operand(val, 0) {
bw.saturating_sub(value_bit_width(&src)) + 1
} else {
1
}
}
Some(Opcode::SExt) => {
if let Some(src) = get_operand(val, 0) {
if let Some(a) = self.op_sb(val, 0, vt, bw) {
(a + bw.saturating_sub(value_bit_width(&src))).min(bw)
} else {
1
}
} else {
1
}
}
Some(Opcode::Phi) => {
let n = num_operands(val);
if n == 0 {
return 1;
}
let mut min_sb = bw;
for i in 0..n {
if let Some(op) = get_operand(val, i) {
let obw = value_bit_width(&op);
let sb = self.compute_nsb_inner(&op, vt, obw);
min_sb = min_sb.min(sb);
}
}
min_sb
}
Some(Opcode::Select) => {
if let (Some(t), Some(f)) = (self.op_sb(val, 1, vt, bw), self.op_sb(val, 2, vt, bw))
{
t.min(f)
} else {
1
}
}
Some(Opcode::ICmp) | Some(Opcode::FCmp) => bw,
_ => 1,
}
}
fn count_sign_bits_u128(&self, val: u128, bw: u32) -> u32 {
if bw == 0 {
return 0;
}
if bw >= 128 {
return count_leading_identical(val as i128);
}
let mask = (1u128 << bw) - 1;
let v = val & mask;
let sign = 1u128 << (bw - 1);
let ext = if (v & sign) != 0 { v | !mask } else { v };
count_leading_identical(ext as i128)
}
fn op_sb(&self, val: &ValueRef, idx: usize, vt: &X86ValueTracking, bw: u32) -> Option<u32> {
let op = get_operand(val, idx)?;
let obw = value_bit_width(&op);
Some(self.compute_nsb_inner(&op, vt, obw).min(bw))
}
fn const_op(&self, val: &ValueRef, idx: usize) -> Option<u64> {
get_constant_u64(&get_operand(val, idx)?)
}
pub fn clear(&mut self) {
self.cache.clear();
}
pub fn invalidate(&mut self, vid: u64) {
self.cache.remove(&vid);
}
pub fn cache_size(&self) -> usize {
self.cache.len()
}
pub fn is_sign_ext_redundant(
&mut self,
val: &ValueRef,
src_bw: u32,
vt: &X86ValueTracking,
) -> bool {
let dst_bw = value_bit_width(val);
self.compute_num_sign_bits(val, vt) >= dst_bw - src_bw + 1
}
pub fn is_known_non_negative(&mut self, val: &ValueRef, vt: &X86ValueTracking) -> bool {
self.compute_num_sign_bits(val, vt) >= 2
}
}
fn count_leading_identical(val: i128) -> u32 {
let v = val as u128;
if v == 0 || v == !0u128 {
return 128;
}
if (v >> 127) & 1 == 1 {
v.leading_ones()
} else {
v.leading_zeros()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86ValueLatticeElement {
Undefined,
Constant(u128),
ConstantRange { lo: u128, hi: u128, is_signed: bool },
NotConstant,
Overdefined,
}
impl X86ValueLatticeElement {
pub fn is_overdefined(&self) -> bool {
matches!(self, Self::Overdefined)
}
pub fn is_undefined(&self) -> bool {
matches!(self, Self::Undefined)
}
pub fn is_constant(&self) -> bool {
matches!(self, Self::Constant(_))
}
pub fn as_constant(&self) -> Option<u128> {
if let Self::Constant(c) = self {
Some(*c)
} else {
None
}
}
pub fn as_range(&self) -> Option<(u128, u128, bool)> {
match self {
Self::ConstantRange { lo, hi, is_signed } => Some((*lo, *hi, *is_signed)),
Self::Constant(c) => Some((*c, *c, false)),
_ => None,
}
}
pub fn contains(&self, val: u128) -> bool {
match self {
Self::Undefined => false,
Self::Constant(c) => *c == val,
Self::ConstantRange { lo, hi, .. } => *lo <= val && val <= *hi,
Self::NotConstant | Self::Overdefined => true,
}
}
pub fn meet(&self, other: &Self) -> Self {
match (self, other) {
(_, Self::Overdefined) | (Self::Overdefined, _) => Self::Overdefined,
(Self::Undefined, x) | (x, Self::Undefined) => x.clone(),
(Self::Constant(a), Self::Constant(b)) => {
if a == b {
Self::Constant(*a)
} else {
Self::Overdefined
}
}
(Self::Constant(c), Self::ConstantRange { lo, hi, .. })
| (Self::ConstantRange { lo, hi, .. }, Self::Constant(c)) => {
if *lo <= *c && *c <= *hi {
Self::Constant(*c)
} else {
Self::Overdefined
}
}
(Self::Constant(c), Self::NotConstant) | (Self::NotConstant, Self::Constant(c)) => {
Self::Constant(*c)
}
(
Self::ConstantRange { lo: l1, hi: h1, .. },
Self::ConstantRange { lo: l2, hi: h2, .. },
) => {
let lo = (*l1).max(*l2);
let hi = (*h1).min(*h2);
if lo <= hi {
if lo == hi {
Self::Constant(lo)
} else {
Self::ConstantRange {
lo,
hi,
is_signed: false,
}
}
} else {
Self::Overdefined
}
}
(r @ Self::ConstantRange { .. }, Self::NotConstant)
| (Self::NotConstant, r @ Self::ConstantRange { .. }) => r.clone(),
(Self::NotConstant, Self::NotConstant) => Self::NotConstant,
}
}
pub fn join(&self, other: &Self) -> Self {
match (self, other) {
(_, Self::Overdefined) | (Self::Overdefined, _) => Self::Overdefined,
(Self::Undefined, x) | (x, Self::Undefined) => x.clone(),
(Self::Constant(a), Self::Constant(b)) => {
if a == b {
Self::Constant(*a)
} else {
Self::ConstantRange {
lo: (*a).min(*b),
hi: (*a).max(*b),
is_signed: false,
}
}
}
(Self::Constant(c), Self::ConstantRange { lo, hi, .. })
| (Self::ConstantRange { lo, hi, .. }, Self::Constant(c)) => Self::ConstantRange {
lo: (*lo).min(*c),
hi: (*hi).max(*c),
is_signed: false,
},
(_, Self::NotConstant) | (Self::NotConstant, _) => Self::NotConstant,
(
Self::ConstantRange { lo: l1, hi: h1, .. },
Self::ConstantRange { lo: l2, hi: h2, .. },
) => Self::ConstantRange {
lo: (*l1).min(*l2),
hi: (*h1).max(*h2),
is_signed: false,
},
(Self::NotConstant, Self::NotConstant) => Self::NotConstant,
}
}
}
impl Default for X86ValueLatticeElement {
fn default() -> Self {
Self::Undefined
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum X86EdgeConstraint {
Eq { value_vid: u64, constant: u128 },
Ne { value_vid: u64, constant: u128 },
Ult { value_vid: u64, constant: u128 },
Ule { value_vid: u64, constant: u128 },
Ugt { value_vid: u64, constant: u128 },
Uge { value_vid: u64, constant: u128 },
Slt { value_vid: u64, constant: u128 },
Sle { value_vid: u64, constant: u128 },
Sgt { value_vid: u64, constant: u128 },
Sge { value_vid: u64, constant: u128 },
}
impl X86EdgeConstraint {
pub fn apply_to(&self, lattice: &X86ValueLatticeElement) -> X86ValueLatticeElement {
match self {
Self::Eq { constant, .. } => lattice.meet(&X86ValueLatticeElement::Constant(*constant)),
Self::Ne { constant, .. } => {
if let X86ValueLatticeElement::Constant(c) = lattice {
if c == constant {
X86ValueLatticeElement::Overdefined
} else {
lattice.clone()
}
} else {
lattice.clone()
}
}
Self::Ult { constant, .. } => {
self.restrict_range(lattice, 0, constant.saturating_sub(1))
}
Self::Ule { constant, .. } => self.restrict_range(lattice, 0, *constant),
Self::Ugt { constant, .. } => {
self.restrict_range(lattice, constant.saturating_add(1), u128::MAX)
}
Self::Uge { constant, .. } => self.restrict_range(lattice, *constant, u128::MAX),
Self::Slt { constant, .. } => {
self.restrict_range_signed(lattice, i128::MIN, (*constant as i128) - 1)
}
Self::Sle { constant, .. } => {
self.restrict_range_signed(lattice, i128::MIN, *constant as i128)
}
Self::Sgt { constant, .. } => {
self.restrict_range_signed(lattice, (*constant as i128) + 1, i128::MAX)
}
Self::Sge { constant, .. } => {
self.restrict_range_signed(lattice, *constant as i128, i128::MAX)
}
}
}
fn restrict_range(
&self,
lattice: &X86ValueLatticeElement,
lo: u128,
hi: u128,
) -> X86ValueLatticeElement {
match lattice {
X86ValueLatticeElement::ConstantRange {
lo: rlo, hi: rhi, ..
} => {
let new_lo = rlo.max(&lo);
let new_hi = rhi.min(&hi);
if new_lo <= new_hi {
if new_lo == new_hi {
X86ValueLatticeElement::Constant(*new_lo)
} else {
X86ValueLatticeElement::ConstantRange {
lo: *new_lo,
hi: *new_hi,
is_signed: false,
}
}
} else {
X86ValueLatticeElement::Overdefined
}
}
X86ValueLatticeElement::Constant(c) => {
if *c >= lo && *c <= hi {
lattice.clone()
} else {
X86ValueLatticeElement::Overdefined
}
}
_ => lattice.clone(),
}
}
fn restrict_range_signed(
&self,
lattice: &X86ValueLatticeElement,
_lo: i128,
_hi: i128,
) -> X86ValueLatticeElement {
match lattice {
X86ValueLatticeElement::ConstantRange { lo, hi, .. } => {
let lo_s = *lo as i128;
let hi_s = *hi as i128;
let nlo = lo_s.max(_lo);
let nhi = hi_s.min(_hi);
if nlo <= nhi {
if nlo == nhi {
X86ValueLatticeElement::Constant(nlo as u128)
} else {
X86ValueLatticeElement::ConstantRange {
lo: nlo as u128,
hi: nhi as u128,
is_signed: true,
}
}
} else {
X86ValueLatticeElement::Overdefined
}
}
X86ValueLatticeElement::Constant(c) => {
let cs = *c as i128;
if cs >= _lo && cs <= _hi {
lattice.clone()
} else {
X86ValueLatticeElement::Overdefined
}
}
_ => lattice.clone(),
}
}
}
pub struct X86LazyValueInfo {
block_value_cache: HashMap<(u64, u64), X86ValueLatticeElement>,
edge_constraints: HashMap<(u64, u64), Vec<X86EdgeConstraint>>,
global_constants: HashMap<u64, u128>,
reaching_defs: HashMap<u64, HashSet<(u64, u64)>>,
block_succs: HashMap<u64, Vec<u64>>,
block_preds: HashMap<u64, Vec<u64>>,
max_depth: u32,
pub num_queries: u64,
pub num_cache_hits: u64,
pub num_constants_found: u64,
}
impl X86LazyValueInfo {
pub fn new() -> Self {
Self {
block_value_cache: HashMap::new(),
edge_constraints: HashMap::new(),
global_constants: HashMap::new(),
reaching_defs: HashMap::new(),
block_succs: HashMap::new(),
block_preds: HashMap::new(),
max_depth: 100,
num_queries: 0,
num_cache_hits: 0,
num_constants_found: 0,
}
}
pub fn build_cfg(&mut self, func: &ValueRef) {
let f = func.borrow();
self.block_succs.clear();
self.block_preds.clear();
for block_ref in &f.blocks {
let block = block_ref.borrow();
let bid = block.vid;
let succ_ids: Vec<u64> = block.successors.iter().map(|s| s.borrow().vid).collect();
self.block_succs.insert(bid, succ_ids.clone());
for sid in &succ_ids {
self.block_preds
.entry(*sid)
.or_insert_with(Vec::new)
.push(bid);
}
}
}
pub fn extract_edge_constraints(&mut self, _func: &ValueRef) {
self.edge_constraints.clear();
}
pub fn derive_constraints_from_icmp(
&self,
cond: &ValueRef,
) -> Option<(X86EdgeConstraint, X86EdgeConstraint)> {
let c = cond.borrow();
if c.opcode != Some(Opcode::ICmp) || c.operands.len() < 3 {
return None;
}
let pred_val = &c.operands[0];
let lhs = &c.operands[1];
let rhs = &c.operands[2];
let pred = self.decode_icmp_pred(pred_val)?;
let lhs_vid = lhs.borrow().vid;
let const_rhs = get_constant_u64(rhs)?;
Some(self.pred_to_constraint(pred, lhs_vid, const_rhs as u128))
}
fn decode_icmp_pred(&self, pred_val: &ValueRef) -> Option<ICmpPred> {
get_constant_u64(pred_val).and_then(|p| match p {
0 => Some(ICmpPred::Eq),
1 => Some(ICmpPred::Ne),
2 => Some(ICmpPred::Ugt),
3 => Some(ICmpPred::Uge),
4 => Some(ICmpPred::Ult),
5 => Some(ICmpPred::Ule),
6 => Some(ICmpPred::Sgt),
7 => Some(ICmpPred::Sge),
8 => Some(ICmpPred::Slt),
9 => Some(ICmpPred::Sle),
_ => return None,
})
}
pub fn pred_to_constraint(
&self,
pred: ICmpPred,
lhs_vid: u64,
constant: u128,
) -> (X86EdgeConstraint, X86EdgeConstraint) {
match pred {
ICmpPred::Eq => (
X86EdgeConstraint::Eq {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Ne {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Ne => (
X86EdgeConstraint::Ne {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Eq {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Ugt => (
X86EdgeConstraint::Ugt {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Ule {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Uge => (
X86EdgeConstraint::Uge {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Ult {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Ult => (
X86EdgeConstraint::Ult {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Uge {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Ule => (
X86EdgeConstraint::Ule {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Ugt {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Sgt => (
X86EdgeConstraint::Sgt {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Sle {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Sge => (
X86EdgeConstraint::Sge {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Slt {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Slt => (
X86EdgeConstraint::Slt {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Sge {
value_vid: lhs_vid,
constant,
},
),
ICmpPred::Sle => (
X86EdgeConstraint::Sle {
value_vid: lhs_vid,
constant,
},
X86EdgeConstraint::Sgt {
value_vid: lhs_vid,
constant,
},
),
}
}
pub fn get_value_at_block(
&mut self,
val: &ValueRef,
block: &ValueRef,
) -> X86ValueLatticeElement {
self.num_queries += 1;
let val_vid = val.borrow().vid;
let block_vid = block.borrow().vid;
let key = (block_vid, val_vid);
if let Some(cached) = self.block_value_cache.get(&key) {
self.num_cache_hits += 1;
return cached.clone();
}
let result = self.compute_value_at_block(val, block);
self.block_value_cache.insert(key, result.clone());
if result.is_constant() {
self.num_constants_found += 1;
}
result
}
fn compute_value_at_block(
&mut self,
val: &ValueRef,
block: &ValueRef,
) -> X86ValueLatticeElement {
let val_vid = val.borrow().vid;
let block_vid = block.borrow().vid;
if let Some(c) = self.global_constants.get(&val_vid) {
return X86ValueLatticeElement::Constant(*c);
}
let preds = self
.block_preds
.get(&block_vid)
.cloned()
.unwrap_or_default();
if preds.is_empty() {
return self.resolve_value_at_entry(val);
}
let mut result = X86ValueLatticeElement::Undefined;
for pred_id in &preds {
let pred_lattice = self.get_value_at_block_exit(val, *pred_id);
let edge_key = (*pred_id, block_vid);
let mut constrained = pred_lattice;
if let Some(constraints) = self.edge_constraints.get(&edge_key) {
for c in constraints {
constrained = c.apply_to(&constrained);
}
}
result = result.join(&constrained);
if result.is_overdefined() {
break;
}
}
if self.is_loop_header(block_vid) {
result = self.widen_lattice(&result);
}
result
}
fn get_value_at_block_exit(&mut self, val: &ValueRef, block_id: u64) -> X86ValueLatticeElement {
let val_vid = val.borrow().vid;
if let Some(defs) = self.reaching_defs.get(&val_vid) {
for (def_bid, _) in defs {
if *def_bid == block_id {
return self.compute_defined_value(val);
}
}
}
let block_ref = make_dummy_ref(block_id, Opcode::Br);
self.get_value_at_block(val, &block_ref)
}
fn compute_defined_value(&mut self, val: &ValueRef) -> X86ValueLatticeElement {
match get_opcode(val) {
Some(Opcode::Phi) => {
let mut result = X86ValueLatticeElement::Undefined;
for i in 0..num_operands(val) {
if let Some(op) = get_operand(val, i) {
let dummy = make_dummy_ref(0, Opcode::Br);
result = result.join(&self.get_value_at_block(&op, &dummy));
if result.is_overdefined() {
break;
}
}
}
result
}
Some(Opcode::Add) => self.binop_lattice(val, X86LazyValueInfo::compute_add_lattice),
Some(Opcode::Sub) => self.binop_lattice(val, X86LazyValueInfo::compute_sub_lattice),
Some(Opcode::Mul) => self.binop_lattice(val, X86LazyValueInfo::compute_mul_lattice),
Some(Opcode::And) => self.binop_lattice(val, X86LazyValueInfo::compute_and_lattice),
Some(Opcode::Or) => self.binop_lattice(val, X86LazyValueInfo::compute_or_lattice),
Some(Opcode::Xor) => self.binop_lattice(val, X86LazyValueInfo::compute_xor_lattice),
Some(Opcode::Select) => {
let t = self.get_op_lattice(val, 1);
let f = self.get_op_lattice(val, 2);
t.join(&f)
}
Some(Opcode::ZExt) | Some(Opcode::SExt) | Some(Opcode::Trunc) => {
self.get_op_lattice(val, 0)
}
Some(Opcode::ICmp) => X86ValueLatticeElement::ConstantRange {
lo: 0,
hi: 1,
is_signed: false,
},
Some(Opcode::Call) | Some(Opcode::Load) => X86ValueLatticeElement::NotConstant,
_ => {
if let Some(c) = get_constant_u64(val) {
X86ValueLatticeElement::Constant(c as u128)
} else {
X86ValueLatticeElement::NotConstant
}
}
}
}
fn binop_lattice(
&mut self,
val: &ValueRef,
f: fn(&X86ValueLatticeElement, &X86ValueLatticeElement) -> X86ValueLatticeElement,
) -> X86ValueLatticeElement {
let a = self.get_op_lattice(val, 0);
let b = self.get_op_lattice(val, 1);
f(&a, &b)
}
fn get_op_lattice(&mut self, val: &ValueRef, idx: usize) -> X86ValueLatticeElement {
if let Some(op) = get_operand(val, idx) {
let dummy = make_dummy_ref(0, Opcode::Br);
self.get_value_at_block(&op, &dummy)
} else {
X86ValueLatticeElement::Overdefined
}
}
fn resolve_value_at_entry(&self, val: &ValueRef) -> X86ValueLatticeElement {
if let Some(c) = get_constant_u64(val) {
X86ValueLatticeElement::Constant(c as u128)
} else {
X86ValueLatticeElement::NotConstant
}
}
fn is_loop_header(&self, block_id: u64) -> bool {
if let Some(succs) = self.block_succs.get(&block_id) {
for sid in succs {
if *sid <= block_id {
if let Some(preds) = self.block_preds.get(&block_id) {
if preds.contains(sid) {
return true;
}
}
}
}
}
false
}
fn widen_lattice(&self, current: &X86ValueLatticeElement) -> X86ValueLatticeElement {
match current {
X86ValueLatticeElement::Constant(c) => X86ValueLatticeElement::ConstantRange {
lo: *c,
hi: *c,
is_signed: false,
},
X86ValueLatticeElement::ConstantRange { lo, hi, .. } => {
if hi.saturating_sub(*lo) < 1024 {
current.clone()
} else {
X86ValueLatticeElement::NotConstant
}
}
_ => current.clone(),
}
}
pub fn set_global_constant(&mut self, val_vid: u64, constant: u128) {
self.global_constants.insert(val_vid, constant);
}
pub fn get_constant_at_block(&mut self, val: &ValueRef, block: &ValueRef) -> Option<u128> {
self.get_value_at_block(val, block).as_constant()
}
pub fn clear(&mut self) {
self.block_value_cache.clear();
self.global_constants.clear();
self.edge_constraints.clear();
self.num_queries = 0;
self.num_cache_hits = 0;
self.num_constants_found = 0;
}
pub fn add_reaching_def(&mut self, val_vid: u64, block_vid: u64, inst_vid: u64) {
self.reaching_defs
.entry(val_vid)
.or_insert_with(HashSet::new)
.insert((block_vid, inst_vid));
}
pub fn add_edge_constraints(
&mut self,
from: u64,
to: u64,
constraints: Vec<X86EdgeConstraint>,
) {
self.edge_constraints
.entry((from, to))
.or_insert_with(Vec::new)
.extend(constraints);
}
pub fn evaluate_predicate_at_block(
&mut self,
pred: ICmpPred,
lhs: &ValueRef,
rhs: &ValueRef,
block: &ValueRef,
) -> Option<bool> {
let ll = self.get_value_at_block(lhs, block);
let rl = self.get_value_at_block(rhs, block);
X86LazyValueInfo::evaluate_icmp_lattice(pred, &ll, &rl)
}
fn evaluate_icmp_lattice(
pred: ICmpPred,
lhs: &X86ValueLatticeElement,
rhs: &X86ValueLatticeElement,
) -> Option<bool> {
if let (Some(lv), Some(rv)) = (lhs.as_constant(), rhs.as_constant()) {
return Some(match pred {
ICmpPred::Eq => lv == rv,
ICmpPred::Ne => lv != rv,
ICmpPred::Ugt => lv > rv,
ICmpPred::Uge => lv >= rv,
ICmpPred::Ult => lv < rv,
ICmpPred::Ule => lv <= rv,
ICmpPred::Sgt => (lv as i128) > (rv as i128),
ICmpPred::Sge => (lv as i128) >= (rv as i128),
ICmpPred::Slt => (lv as i128) < (rv as i128),
ICmpPred::Sle => (lv as i128) <= (rv as i128),
});
}
if let (Some((l1, h1, _)), Some((l2, h2, _))) = (lhs.as_range(), rhs.as_range()) {
return match pred {
ICmpPred::Eq => {
if l1 == h1 && l2 == h2 && l1 == l2 {
Some(true)
} else if h1 < l2 || h2 < l1 {
Some(false)
} else {
None
}
}
ICmpPred::Ne => {
if h1 < l2 || h2 < l1 {
Some(true)
} else {
None
}
}
ICmpPred::Ult => {
if h1 < l2 {
Some(true)
} else if l1 >= h2 {
Some(false)
} else {
None
}
}
ICmpPred::Ule => {
if h1 <= l2 {
Some(true)
} else if l1 > h2 {
Some(false)
} else {
None
}
}
ICmpPred::Ugt => {
if l1 > h2 {
Some(true)
} else if h1 <= l2 {
Some(false)
} else {
None
}
}
ICmpPred::Uge => {
if l1 >= h2 {
Some(true)
} else if h1 < l2 {
Some(false)
} else {
None
}
}
ICmpPred::Slt => {
if (h1 as i128) < (l2 as i128) {
Some(true)
} else if (l1 as i128) >= (h2 as i128) {
Some(false)
} else {
None
}
}
ICmpPred::Sle => {
if (h1 as i128) <= (l2 as i128) {
Some(true)
} else if (l1 as i128) > (h2 as i128) {
Some(false)
} else {
None
}
}
ICmpPred::Sgt => {
if (l1 as i128) > (h2 as i128) {
Some(true)
} else if (h1 as i128) <= (l2 as i128) {
Some(false)
} else {
None
}
}
ICmpPred::Sge => {
if (l1 as i128) >= (h2 as i128) {
Some(true)
} else if (h1 as i128) < (l2 as i128) {
Some(false)
} else {
None
}
}
};
}
None
}
pub fn run_cvp(&mut self, func: &ValueRef) -> u32 {
let mut replacements = 0u32;
let f = func.borrow();
for block_ref in &f.blocks {
let block = block_ref.borrow();
for inst_ref in &block.instructions {
for op_ref in &inst_ref.borrow().operands {
if op_ref.borrow().is_constant() {
continue;
}
let l = self.get_value_at_block(op_ref, block_ref);
if l.as_constant().is_some() {
replacements += 1;
}
}
}
}
replacements
}
pub fn run_jump_threading(&mut self, func: &ValueRef) -> u32 {
let mut threaded = 0u32;
let f = func.borrow();
for block_ref in &f.blocks {
let block = block_ref.borrow();
if block.opcode != Some(Opcode::Br) || block.operands.len() < 3 {
continue;
}
let cond = &block.operands[0];
if let Some(result) = self.evaluate_branch_condition(cond, block_ref) {
threaded += 1;
}
}
threaded
}
fn evaluate_branch_condition(&mut self, cond: &ValueRef, block: &ValueRef) -> Option<bool> {
let c = cond.borrow();
if c.opcode == Some(Opcode::ICmp) && c.operands.len() >= 3 {
let pred = self.decode_icmp_pred(&c.operands[0])?;
return self.evaluate_predicate_at_block(pred, &c.operands[1], &c.operands[2], block);
}
self.get_value_at_block(cond, block)
.as_constant()
.map(|c| c != 0)
}
fn compute_add_lattice(
a: &X86ValueLatticeElement,
b: &X86ValueLatticeElement,
) -> X86ValueLatticeElement {
match (a, b) {
(X86ValueLatticeElement::Constant(av), X86ValueLatticeElement::Constant(bv)) => {
X86ValueLatticeElement::Constant(av.wrapping_add(*bv))
}
(
X86ValueLatticeElement::Constant(c),
X86ValueLatticeElement::ConstantRange { lo, hi, .. },
)
| (
X86ValueLatticeElement::ConstantRange { lo, hi, .. },
X86ValueLatticeElement::Constant(c),
) => {
let lo = lo.wrapping_add(*c);
let hi = hi.wrapping_add(*c);
if lo == hi {
X86ValueLatticeElement::Constant(lo)
} else {
X86ValueLatticeElement::ConstantRange {
lo,
hi,
is_signed: false,
}
}
}
(
X86ValueLatticeElement::ConstantRange { lo: l1, hi: h1, .. },
X86ValueLatticeElement::ConstantRange { lo: l2, hi: h2, .. },
) => X86ValueLatticeElement::ConstantRange {
lo: l1.wrapping_add(*l2),
hi: h1.wrapping_add(*h2),
is_signed: false,
},
_ => X86ValueLatticeElement::NotConstant,
}
}
fn compute_sub_lattice(
a: &X86ValueLatticeElement,
b: &X86ValueLatticeElement,
) -> X86ValueLatticeElement {
match (a, b) {
(X86ValueLatticeElement::Constant(av), X86ValueLatticeElement::Constant(bv)) => {
X86ValueLatticeElement::Constant(av.wrapping_sub(*bv))
}
(
X86ValueLatticeElement::ConstantRange { lo: l1, hi: h1, .. },
X86ValueLatticeElement::ConstantRange { lo: l2, hi: h2, .. },
) => X86ValueLatticeElement::ConstantRange {
lo: l1.wrapping_sub(*h2),
hi: h1.wrapping_sub(*l2),
is_signed: false,
},
_ => X86ValueLatticeElement::NotConstant,
}
}
fn compute_mul_lattice(
a: &X86ValueLatticeElement,
b: &X86ValueLatticeElement,
) -> X86ValueLatticeElement {
match (a, b) {
(X86ValueLatticeElement::Constant(av), X86ValueLatticeElement::Constant(bv)) => {
X86ValueLatticeElement::Constant(av.wrapping_mul(*bv))
}
(
X86ValueLatticeElement::ConstantRange { lo: l1, hi: h1, .. },
X86ValueLatticeElement::ConstantRange { lo: l2, hi: h2, .. },
) => {
let ps = [
l1.wrapping_mul(*l2),
l1.wrapping_mul(*h2),
h1.wrapping_mul(*l2),
h1.wrapping_mul(*h2),
];
let lo = ps.iter().min().copied().unwrap_or(0);
let hi = ps.iter().max().copied().unwrap_or(0);
X86ValueLatticeElement::ConstantRange {
lo,
hi,
is_signed: false,
}
}
_ => X86ValueLatticeElement::NotConstant,
}
}
fn compute_and_lattice(
a: &X86ValueLatticeElement,
b: &X86ValueLatticeElement,
) -> X86ValueLatticeElement {
match (a, b) {
(X86ValueLatticeElement::Constant(av), X86ValueLatticeElement::Constant(bv)) => {
X86ValueLatticeElement::Constant(av & bv)
}
(
X86ValueLatticeElement::Range { lo: _, hi, .. },
X86ValueLatticeElement::Range { lo: _, hi: h2, .. },
) => {
let hi = hi.min(*h2);
X86ValueLatticeElement::ConstantRange {
lo: 0,
hi,
is_signed: false,
}
}
_ => X86ValueLatticeElement::NotConstant,
}
}
fn compute_or_lattice(
a: &X86ValueLatticeElement,
b: &X86ValueLatticeElement,
) -> X86ValueLatticeElement {
match (a, b) {
(X86ValueLatticeElement::Constant(av), X86ValueLatticeElement::Constant(bv)) => {
X86ValueLatticeElement::Constant(av | bv)
}
(
X86ValueLatticeElement::ConstantRange { lo: l1, hi: _, .. },
X86ValueLatticeElement::ConstantRange { lo: l2, hi: _, .. },
) => X86ValueLatticeElement::ConstantRange {
lo: (*l1).max(*l2),
hi: u128::MAX,
is_signed: false,
},
_ => X86ValueLatticeElement::NotConstant,
}
}
fn compute_xor_lattice(
a: &X86ValueLatticeElement,
b: &X86ValueLatticeElement,
) -> X86ValueLatticeElement {
match (a, b) {
(X86ValueLatticeElement::Constant(av), X86ValueLatticeElement::Constant(bv)) => {
X86ValueLatticeElement::Constant(av ^ bv)
}
_ => X86ValueLatticeElement::NotConstant,
}
}
}
impl Default for X86LazyValueInfo {
fn default() -> Self {
Self::new()
}
}
pub struct X86DemandedBits {
demanded: HashMap<u64, u128>,
bit_widths: HashMap<u64, u32>,
worklist: VecDeque<u64>,
max_bit_width: u32,
}
impl X86DemandedBits {
pub fn new(max_bit_width: u32) -> Self {
Self {
demanded: HashMap::new(),
bit_widths: HashMap::new(),
worklist: VecDeque::new(),
max_bit_width,
}
}
pub fn default_for_x86_64() -> Self {
Self::new(64)
}
pub fn run_on_function(&mut self, func: &ValueRef) {
let f = func.borrow();
self.demanded.clear();
self.worklist.clear();
for block_ref in &f.blocks {
let block = block_ref.borrow();
for inst_ref in &block.instructions {
let op = inst_ref.borrow().opcode;
match op {
Some(Opcode::Ret) | Some(Opcode::Store) | Some(Opcode::Call) => {
for o in &inst_ref.borrow().operands {
let vid = o.borrow().vid;
let bw = value_bit_width(o);
self.bit_widths.insert(vid, bw);
let m = if bw >= self.max_bit_width {
!0u128
} else {
(1u128 << bw) - 1
};
self.demanded.insert(vid, m);
self.worklist.push_back(vid);
}
}
_ => {}
}
}
}
let mut visited = HashSet::new();
while let Some(vid) = self.worklist.pop_front() {
if !visited.insert(vid) {
continue;
}
let demand = self.demanded.get(&vid).copied().unwrap_or(!0u128);
let dummy = make_dummy_ref(vid, Opcode::Add);
let op = get_opcode(&dummy);
self.propagate_demanded(&dummy, demand, op);
}
}
fn propagate_demanded(&mut self, val: &ValueRef, demand: u128, op: Option<Opcode>) {
match op {
Some(Opcode::Add) | Some(Opcode::Sub) | Some(Opcode::Mul) | Some(Opcode::And)
| Some(Opcode::Or) | Some(Opcode::Xor) => {
let hi = 128 - demand.leading_zeros();
let mask = if hi >= 128 { !0u128 } else { (1u128 << hi) - 1 } | demand;
for i in 0..2 {
if let Some(o) = get_operand(val, i) {
self.add_demand(&o, mask);
}
}
}
Some(Opcode::Shl) => {
if let Some(o) = get_operand(val, 0) {
if let Some(sh) = get_constant_u64(
&get_operand(val, 1).unwrap_or_else(|| make_dummy_ref(0, Opcode::Add)),
) {
self.add_demand(&o, demand >> sh);
} else {
self.add_demand(&o, !0u128);
}
}
}
Some(Opcode::LShr) | Some(Opcode::AShr) => {
if let Some(o) = get_operand(val, 0) {
if let Some(sh) = get_constant_u64(
&get_operand(val, 1).unwrap_or_else(|| make_dummy_ref(0, Opcode::Add)),
) {
self.add_demand(&o, demand << sh);
} else {
self.add_demand(&o, !0u128);
}
}
}
Some(Opcode::Trunc) => {
if let Some(o) = get_operand(val, 0) {
let dst_bw = value_bit_width(val);
self.add_demand(&o, demand & ((1u128 << dst_bw) - 1));
}
}
Some(Opcode::ZExt) | Some(Opcode::SExt) => {
if let Some(o) = get_operand(val, 0) {
let src_bw = value_bit_width(&o);
self.add_demand(&o, demand & ((1u128 << src_bw) - 1));
}
}
Some(Opcode::Select) => {
if let Some(c) = get_operand(val, 0) {
self.add_demand(&c, 1);
}
for i in 1..=2 {
if let Some(o) = get_operand(val, i) {
self.add_demand(&o, demand);
}
}
}
Some(Opcode::Phi) => {
for i in 0..num_operands(val) {
if let Some(o) = get_operand(val, i) {
self.add_demand(&o, demand);
}
}
}
_ => {
for i in 0..num_operands(val) {
if let Some(o) = get_operand(val, i) {
self.add_demand(&o, !0u128);
}
}
}
}
}
fn add_demand(&mut self, val: &ValueRef, mask: u128) {
let vid = val.borrow().vid;
let cur = self.demanded.get(&vid).copied().unwrap_or(0);
let new = cur | mask;
if new != cur {
self.demanded.insert(vid, new);
self.bit_widths
.entry(vid)
.or_insert_with(|| value_bit_width(val));
self.worklist.push_back(vid);
}
}
pub fn get_demanded_bits(&self, val: &ValueRef) -> u128 {
self.demanded
.get(&val.borrow().vid)
.copied()
.unwrap_or(!0u128)
}
pub fn is_only_demanded_low_bits(&self, val: &ValueRef, low: u32) -> bool {
let m = self.get_demanded_bits(val);
m != 0 && m < (1u128 << low)
}
pub fn is_only_demanded_high_bits(&self, val: &ValueRef, high: u32) -> bool {
let m = self.get_demanded_bits(val);
let bw = self
.bit_widths
.get(&val.borrow().vid)
.copied()
.unwrap_or(64);
let lb = bw - high;
(m >> lb) != 0 && (m & ((1u128 << lb) - 1)) == 0
}
pub fn count_demanded_bits(&self, val: &ValueRef) -> u32 {
self.get_demanded_bits(val).count_ones()
}
pub fn set_demanded_bits(&mut self, val: &ValueRef, mask: u128) {
let vid = val.borrow().vid;
self.demanded.insert(vid, mask);
self.bit_widths
.entry(vid)
.or_insert_with(|| value_bit_width(val));
}
pub fn set_bit_width(&mut self, val: &ValueRef, w: u32) {
self.bit_widths.insert(val.borrow().vid, w);
}
pub fn clear(&mut self) {
self.demanded.clear();
self.bit_widths.clear();
self.worklist.clear();
}
pub fn simplify_instruction(&self, val: &ValueRef, kb: &X86KnownBits) -> Option<X86KnownBits> {
let demand = self.get_demanded_bits(val);
let op = get_opcode(val);
match op {
Some(Opcode::ZExt) | Some(Opcode::SExt) => {
if let Some(src) = get_operand(val, 0) {
let sbw = value_bit_width(&src);
if (demand & !((1u128 << sbw) - 1)) == 0 {
return Some(X86KnownBits::compute_trunc(kb, sbw));
}
}
None
}
_ => None,
}
}
pub fn get_demanded_bit_width(&self, val: &ValueRef) -> Option<u32> {
let m = self.get_demanded_bits(val);
if m == 0 || m == !0u128 {
return None;
}
let hi = 128 - m.leading_zeros();
let rounded = if hi <= 8 {
8
} else if hi <= 16 {
16
} else if hi <= 32 {
32
} else if hi <= 64 {
64
} else {
128
};
let cur = value_bit_width(val);
if rounded < cur {
Some(rounded)
} else {
None
}
}
}
impl Default for X86DemandedBits {
fn default() -> Self {
Self::default_for_x86_64()
}
}
pub struct X86ValueMapper {
value_map: HashMap<u64, u64>,
type_map: HashMap<u64, u64>,
equivalence_classes: HashMap<u64, HashSet<u64>>,
canonical_rep: HashMap<u64, u64>,
constant_pool: HashMap<(u32, Vec<u64>), u64>,
block_map: HashMap<u64, u64>,
next_vid: u64,
}
impl X86ValueMapper {
pub fn new() -> Self {
Self {
value_map: HashMap::new(),
type_map: HashMap::new(),
equivalence_classes: HashMap::new(),
canonical_rep: HashMap::new(),
constant_pool: HashMap::new(),
block_map: HashMap::new(),
next_vid: 1_000_000,
}
}
pub fn map_value(&self, vid: u64) -> u64 {
self.value_map.get(&vid).copied().unwrap_or(vid)
}
pub fn map_value_ref(&self, val: &ValueRef) -> u64 {
self.map_value(val.borrow().vid)
}
pub fn register_value(&mut self, old: u64, new: u64) {
self.value_map.insert(old, new);
}
pub fn map_type(&self, tid: u64) -> u64 {
self.type_map.get(&tid).copied().unwrap_or(tid)
}
pub fn register_type(&mut self, old: u64, new: u64) {
self.type_map.insert(old, new);
}
pub fn create_equivalence_class(&mut self, canonical: u64) {
self.equivalence_classes
.entry(canonical)
.or_insert_with(HashSet::new)
.insert(canonical);
self.canonical_rep.insert(canonical, canonical);
}
pub fn add_to_equivalence_class(&mut self, canonical: u64, value: u64) {
self.equivalence_classes
.entry(canonical)
.or_insert_with(HashSet::new)
.insert(value);
self.canonical_rep.insert(value, canonical);
}
pub fn get_canonical(&self, vid: u64) -> u64 {
self.canonical_rep.get(&vid).copied().unwrap_or(vid)
}
pub fn are_equivalent(&self, a: u64, b: u64) -> bool {
if a == b {
return true;
}
self.get_canonical(a) == self.get_canonical(b)
}
pub fn get_equivalent_values(&self, vid: u64) -> Vec<u64> {
let c = self.get_canonical(vid);
self.equivalence_classes
.get(&c)
.map(|s| s.iter().cloned().collect())
.unwrap_or_else(|| vec![vid])
}
pub fn try_cse(&self, opcode: Opcode, operands: &[u64]) -> Option<u64> {
let key = (opcode as u32, operands.to_vec());
self.constant_pool.get(&key).copied()
}
pub fn record_cse(&mut self, opcode: Opcode, operands: &[u64], result: u64) {
let key = (opcode as u32, operands.to_vec());
self.constant_pool.insert(key, result);
}
pub fn map_block(&self, bid: u64) -> u64 {
self.block_map.get(&bid).copied().unwrap_or(bid)
}
pub fn register_block(&mut self, old: u64, new: u64) {
self.block_map.insert(old, new);
}
pub fn allocate_vid(&mut self) -> u64 {
let v = self.next_vid;
self.next_vid += 1;
v
}
pub fn clear(&mut self) {
self.value_map.clear();
self.type_map.clear();
self.equivalence_classes.clear();
self.canonical_rep.clear();
self.constant_pool.clear();
self.block_map.clear();
}
pub fn num_mapped_values(&self) -> usize {
self.value_map.len()
}
pub fn num_equivalence_classes(&self) -> usize {
self.equivalence_classes.len()
}
}
impl Default for X86ValueMapper {
fn default() -> Self {
Self::new()
}
}
pub struct X86ValueTracking {
pub known_bits: HashMap<u64, X86KnownBits>,
pub num_sign_bits: HashMap<u64, u32>,
pub lvi: X86LazyValueInfo,
pub demanded: X86DemandedBits,
pub mapper: X86ValueMapper,
pub sign_analyzer: X86ComputeNumSignBits,
pub analyzed: bool,
pub stats: X86ValueTrackingStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86ValueTrackingStats {
pub num_values_analyzed: u64,
pub num_known_bits_computed: u64,
pub num_sign_bits_computed: u64,
pub num_lvi_queries: u64,
pub num_demanded_bits_computed: u64,
pub num_cse_hits: u64,
}
impl X86ValueTracking {
pub fn new() -> Self {
Self {
known_bits: HashMap::new(),
num_sign_bits: HashMap::new(),
lvi: X86LazyValueInfo::new(),
demanded: X86DemandedBits::default_for_x86_64(),
mapper: X86ValueMapper::new(),
sign_analyzer: X86ComputeNumSignBits::default_for_x86_64(),
analyzed: false,
stats: X86ValueTrackingStats::default(),
}
}
pub fn run_on_function(&mut self, func: &ValueRef) {
self.analyzed = true;
self.lvi.build_cfg(func);
self.lvi.extract_edge_constraints(func);
self.demanded.run_on_function(func);
self.analyze_all_values(func);
}
fn analyze_all_values(&mut self, func: &ValueRef) {
let f = func.borrow();
let mut worklist: VecDeque<ValueRef> = VecDeque::new();
let mut visited: HashSet<u64> = HashSet::new();
for block_ref in &f.blocks {
worklist.push_back(block_ref.clone());
let block = block_ref.borrow();
for inst_ref in &block.instructions {
worklist.push_back(inst_ref.clone());
}
}
while let Some(val_ref) = worklist.pop_front() {
let vid = val_ref.borrow().vid;
if !visited.insert(vid) {
continue;
}
self.compute_known_bits_for_value(&val_ref);
self.compute_sign_bits_for_value(&val_ref);
for op_ref in &val_ref.borrow().operands {
if !visited.contains(&op_ref.borrow().vid) {
worklist.push_back(op_ref.clone());
}
}
}
}
fn compute_known_bits_for_value(&mut self, val: &ValueRef) {
let vid = val.borrow().vid;
if self.known_bits.contains_key(&vid) {
return;
}
let kb = self.compute_known_bits_inner(val);
self.known_bits.insert(vid, kb);
self.stats.num_known_bits_computed += 1;
}
fn compute_known_bits_inner(&self, val: &ValueRef) -> X86KnownBits {
if let Some(c) = get_constant_u64(val) {
return X86KnownBits::constant(c as u128);
}
match get_opcode(val) {
Some(Opcode::Add) => {
X86KnownBits::compute_add(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::Sub) => {
X86KnownBits::compute_sub(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::Mul) => {
X86KnownBits::compute_mul(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::And) => {
X86KnownBits::compute_and(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::Or) => {
X86KnownBits::compute_or(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::Xor) => {
X86KnownBits::compute_xor(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::Shl) => {
let a = self.get_kb_op(val, 0);
if let Some(sh) = get_constant_u64(
&get_operand(val, 1).unwrap_or_else(|| make_dummy_ref(0, Opcode::Add)),
) {
X86KnownBits::compute_shl(&a, sh as u32, value_bit_width(val))
} else {
X86KnownBits::unknown()
}
}
Some(Opcode::LShr) => {
let a = self.get_kb_op(val, 0);
if let Some(sh) = get_constant_u64(
&get_operand(val, 1).unwrap_or_else(|| make_dummy_ref(0, Opcode::Add)),
) {
X86KnownBits::compute_lshr(&a, sh as u32, value_bit_width(val))
} else {
X86KnownBits::unknown()
}
}
Some(Opcode::AShr) => {
let a = self.get_kb_op(val, 0);
if let Some(sh) = get_constant_u64(
&get_operand(val, 1).unwrap_or_else(|| make_dummy_ref(0, Opcode::Add)),
) {
X86KnownBits::compute_ashr(&a, sh as u32, value_bit_width(val))
} else {
X86KnownBits::unknown()
}
}
Some(Opcode::Select) => X86KnownBits::compute_select(
&self.get_kb_op(val, 0),
&self.get_kb_op(val, 1),
&self.get_kb_op(val, 2),
),
Some(Opcode::Trunc) => {
X86KnownBits::compute_trunc(&self.get_kb_op(val, 0), value_bit_width(val))
}
Some(Opcode::ZExt) => {
let src = get_operand(val, 0).unwrap_or_else(|| make_dummy_ref(0, Opcode::Add));
X86KnownBits::compute_zext(&self.get_kb_op(val, 0), value_bit_width(&src))
}
Some(Opcode::SExt) => {
let src = get_operand(val, 0).unwrap_or_else(|| make_dummy_ref(0, Opcode::Add));
X86KnownBits::compute_sext(&self.get_kb_op(val, 0), value_bit_width(&src))
}
Some(Opcode::ICmp) => {
let pred = get_operand(val, 0)
.and_then(|pv| get_constant_u64(&pv))
.map(|p| match p {
0 => ICmpPred::Eq,
1 => ICmpPred::Ne,
2 => ICmpPred::Ugt,
3 => ICmpPred::Uge,
4 => ICmpPred::Ult,
5 => ICmpPred::Ule,
6 => ICmpPred::Sgt,
7 => ICmpPred::Sge,
8 => ICmpPred::Slt,
9 => ICmpPred::Sle,
_ => ICmpPred::Eq,
})
.unwrap_or(ICmpPred::Eq);
X86KnownBits::compute_icmp(pred, &self.get_kb_op(val, 1), &self.get_kb_op(val, 2))
}
Some(Opcode::FCmp) => {
X86KnownBits::compute_fcmp(1, &self.get_kb_op(val, 1), &self.get_kb_op(val, 2))
}
Some(Opcode::Phi) => {
let n = num_operands(val);
let mut inc: Vec<X86KnownBits> = Vec::with_capacity(n);
for i in 0..n {
inc.push(self.get_kb_op(val, i));
}
X86KnownBits::compute_phi(&inc)
}
Some(Opcode::Call) => X86KnownBits::compute_call(),
Some(Opcode::Load) => X86KnownBits::compute_load(),
Some(Opcode::UDiv) => {
X86KnownBits::compute_udiv(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::SDiv) => {
X86KnownBits::compute_sdiv(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::URem) => {
X86KnownBits::compute_urem(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::SRem) => {
X86KnownBits::compute_srem(&self.get_kb_op(val, 0), &self.get_kb_op(val, 1))
}
Some(Opcode::Freeze) => X86KnownBits::compute_freeze(&self.get_kb_op(val, 0)),
_ => X86KnownBits::unknown(),
}
}
fn get_kb_op(&self, val: &ValueRef, idx: usize) -> X86KnownBits {
get_operand(val, idx)
.map(|op| {
self.known_bits
.get(&op.borrow().vid)
.copied()
.unwrap_or(X86KnownBits::unknown())
})
.unwrap_or(X86KnownBits::unknown())
}
fn compute_sign_bits_for_value(&mut self, val: &ValueRef) {
let vid = val.borrow().vid;
if self.num_sign_bits.contains_key(&vid) {
return;
}
let nsb = self.sign_analyzer.compute_num_sign_bits(val, self);
self.num_sign_bits.insert(vid, nsb);
self.stats.num_sign_bits_computed += 1;
}
pub fn get_known_bits(&self, val: &ValueRef) -> X86KnownBits {
self.known_bits
.get(&val.borrow().vid)
.copied()
.unwrap_or(X86KnownBits::unknown())
}
pub fn get_known_bits_by_id(&self, vid: u64) -> X86KnownBits {
self.known_bits
.get(&vid)
.copied()
.unwrap_or(X86KnownBits::unknown())
}
pub fn get_num_sign_bits(&self, val: &ValueRef) -> u32 {
self.num_sign_bits
.get(&val.borrow().vid)
.copied()
.unwrap_or(1)
}
pub fn is_known_constant(&self, val: &ValueRef) -> Option<u128> {
self.get_known_bits(val).get_constant()
}
pub fn is_known_zero(&self, val: &ValueRef) -> bool {
self.get_known_bits(val).is_zero()
}
pub fn is_known_all_ones(&self, val: &ValueRef) -> bool {
self.get_known_bits(val).is_all_ones()
}
pub fn is_known_non_negative(&self, val: &ValueRef) -> bool {
self.get_known_bits(val).is_known_non_negative()
}
pub fn is_known_negative(&self, val: &ValueRef) -> bool {
self.get_known_bits(val).is_known_negative()
}
pub fn get_lvi_at_block(&mut self, val: &ValueRef, block: &ValueRef) -> X86ValueLatticeElement {
self.lvi.get_value_at_block(val, block)
}
pub fn simplify_instruction(&self, val: &ValueRef) -> Option<u128> {
self.get_known_bits(val).get_constant()
}
pub fn clear(&mut self) {
self.known_bits.clear();
self.num_sign_bits.clear();
self.lvi.clear();
self.demanded.clear();
self.mapper.clear();
self.analyzed = false;
self.stats = X86ValueTrackingStats::default();
}
pub fn get_stats(&self) -> &X86ValueTrackingStats {
&self.stats
}
}
impl Default for X86ValueTracking {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_known_bits_unknown() {
let kb = X86KnownBits::unknown();
assert_eq!(kb.zero, 0);
assert_eq!(kb.one, 0);
assert!(!kb.is_constant());
assert!(!kb.has_known_bits());
}
#[test]
fn test_known_bits_constant() {
let kb = X86KnownBits::constant(42);
assert_eq!(kb.zero, !42u128);
assert_eq!(kb.one, 42);
assert!(kb.is_constant());
assert_eq!(kb.get_constant(), Some(42));
}
#[test]
fn test_known_bits_bounds() {
let kb = X86KnownBits::bounds(7);
assert!(kb.zero & (1u128 << 127) != 0);
}
#[test]
fn test_known_bits_range() {
let kb = X86KnownBits::range(10, 20);
assert!(kb.known_mask() != 0);
}
#[test]
fn test_known_bits_is_zero() {
assert!(X86KnownBits::constant(0).is_zero());
}
#[test]
fn test_known_bits_is_all_ones() {
assert!(X86KnownBits::constant(!0u128).is_all_ones());
}
#[test]
fn test_known_bits_and() {
let a = X86KnownBits::constant(0b1100);
let b = X86KnownBits::constant(0b1010);
assert_eq!(
X86KnownBits::compute_and(&a, &b).get_constant(),
Some(0b1000)
);
}
#[test]
fn test_known_bits_or() {
let a = X86KnownBits::constant(0b1100);
let b = X86KnownBits::constant(0b1010);
assert_eq!(
X86KnownBits::compute_or(&a, &b).get_constant(),
Some(0b1110)
);
}
#[test]
fn test_known_bits_xor() {
let a = X86KnownBits::constant(0b1100);
let b = X86KnownBits::constant(0b1010);
assert_eq!(
X86KnownBits::compute_xor(&a, &b).get_constant(),
Some(0b0110)
);
}
#[test]
fn test_known_bits_shl() {
let a = X86KnownBits::constant(0b0011);
assert_eq!(
X86KnownBits::compute_shl(&a, 2, 64).get_constant(),
Some(0b1100)
);
}
#[test]
fn test_known_bits_shl_overflow() {
let a = X86KnownBits::constant(0b0011);
assert_eq!(
X86KnownBits::compute_shl(&a, 64, 64).get_constant(),
Some(0)
);
}
#[test]
fn test_known_bits_lshr() {
let a = X86KnownBits::constant(0b1100);
assert_eq!(
X86KnownBits::compute_lshr(&a, 2, 64).get_constant(),
Some(0b0011)
);
}
#[test]
fn test_known_bits_ashr_positive() {
let a = X86KnownBits::constant(0b0100);
assert_eq!(
X86KnownBits::compute_ashr(&a, 2, 64).get_constant(),
Some(0b0001)
);
}
#[test]
fn test_known_bits_add_constants() {
let a = X86KnownBits::constant(5);
let b = X86KnownBits::constant(3);
let r = X86KnownBits::compute_add(&a, &b);
assert!(r.has_known_bits());
}
#[test]
fn test_known_bits_mul_zero() {
let a = X86KnownBits::constant(0);
let b = X86KnownBits::unknown();
assert!(X86KnownBits::compute_mul(&a, &b).is_zero());
}
#[test]
fn test_known_bits_mul_one() {
let a = X86KnownBits::constant(42);
let b = X86KnownBits::constant(1);
assert_eq!(X86KnownBits::compute_mul(&a, &b).get_constant(), Some(42));
}
#[test]
fn test_known_bits_select() {
let cond = X86KnownBits::constant(1);
let t = X86KnownBits::constant(100);
let f = X86KnownBits::constant(200);
assert_eq!(
X86KnownBits::compute_select(&cond, &t, &f).get_constant(),
Some(100)
);
let cond0 = X86KnownBits::constant(0);
assert_eq!(
X86KnownBits::compute_select(&cond0, &t, &f).get_constant(),
Some(200)
);
}
#[test]
fn test_known_bits_trunc() {
assert_eq!(
X86KnownBits::compute_trunc(&X86KnownBits::constant(0xABCD), 8).get_constant(),
Some(0xCD)
);
}
#[test]
fn test_known_bits_zext() {
let r = X86KnownBits::compute_zext(&X86KnownBits::constant(0xFF), 8);
assert_eq!((r.zero >> 8) & 0xFF, 0xFF);
}
#[test]
fn test_known_bits_sext_positive() {
let r = X86KnownBits::compute_sext(&X86KnownBits::constant(0x7F), 8);
assert_eq!((r.zero >> 8) & 0xFF, 0xFF);
}
#[test]
fn test_known_bits_sext_negative() {
let r = X86KnownBits::compute_sext(&X86KnownBits::constant(0x80), 8);
assert_eq!((r.one >> 8) & 0xFF, 0xFF);
}
#[test]
fn test_known_bits_phi() {
let r = X86KnownBits::compute_phi(&[
X86KnownBits::constant(10),
X86KnownBits::constant(10),
X86KnownBits::unknown(),
]);
assert_eq!(r.zero & 0xF, 0); }
#[test]
fn test_known_bits_intersect() {
let a = X86KnownBits::new(0xF0, 0x0F);
let b = X86KnownBits::new(0xFF, 0x00);
let r = X86KnownBits::intersect(&a, &b);
assert_eq!(r.zero, 0xF0);
assert_eq!(r.one, 0x00);
}
#[test]
fn test_known_bits_subset() {
let a = X86KnownBits::constant(42);
let b = X86KnownBits::unknown();
assert!(a.is_subset_of(&b));
assert!(!b.is_subset_of(&a));
}
#[test]
fn test_known_bits_consistent() {
let a = X86KnownBits::new(0xF0, 0x00);
let b = X86KnownBits::new(0x0F, 0x00);
assert!(a.is_consistent_with(&b));
}
#[test]
fn test_known_bits_inconsistent() {
let a = X86KnownBits::new(0x00, 0x01);
let b = X86KnownBits::new(0x01, 0x00);
assert!(!a.is_consistent_with(&b));
}
#[test]
fn test_known_bits_icmp_eq() {
assert_eq!(
X86KnownBits::compute_icmp(
ICmpPred::Eq,
&X86KnownBits::constant(5),
&X86KnownBits::constant(5)
)
.get_constant(),
Some(1)
);
}
#[test]
fn test_known_bits_icmp_ne() {
assert_eq!(
X86KnownBits::compute_icmp(
ICmpPred::Ne,
&X86KnownBits::constant(5),
&X86KnownBits::constant(3)
)
.get_constant(),
Some(1)
);
}
#[test]
fn test_known_bits_udiv() {
assert_eq!(
X86KnownBits::compute_udiv(&X86KnownBits::constant(10), &X86KnownBits::constant(3))
.get_constant(),
Some(3)
);
}
#[test]
fn test_known_bits_urem() {
assert_eq!(
X86KnownBits::compute_urem(&X86KnownBits::constant(10), &X86KnownBits::constant(3))
.get_constant(),
Some(1)
);
}
#[test]
fn test_num_sign_bits_positive() {
let nsb = X86ComputeNumSignBits::new(64);
assert_eq!(nsb.count_sign_bits_u128(0x007F, 32), 25);
}
#[test]
fn test_num_sign_bits_zero() {
let nsb = X86ComputeNumSignBits::new(64);
assert_eq!(nsb.count_sign_bits_u128(0, 32), 32);
}
#[test]
fn test_num_sign_bits_all_ones() {
let nsb = X86ComputeNumSignBits::new(64);
let ao = (1u128 << 32) - 1;
assert_eq!(nsb.count_sign_bits_u128(ao, 32), 32);
}
#[test]
fn test_num_sign_bits_cache() {
let mut nsb = X86ComputeNumSignBits::new(64);
let val = make_const_i32(200, 42, "");
let mut vt = X86ValueTracking::new();
let a = nsb.compute_num_sign_bits(&val, &vt);
let b = nsb.compute_num_sign_bits(&val, &vt);
assert_eq!(a, b);
}
#[test]
fn test_num_sign_bits_clear() {
let mut nsb = X86ComputeNumSignBits::new(64);
let val = make_const_i32(300, 0, "");
let mut vt = X86ValueTracking::new();
nsb.compute_num_sign_bits(&val, &vt);
assert_eq!(nsb.cache_size(), 1);
nsb.clear();
assert_eq!(nsb.cache_size(), 0);
}
#[test]
fn test_count_leading_identical() {
assert_eq!(count_leading_identical(0i128), 128);
assert_eq!(count_leading_identical(-1i128), 128);
assert_eq!(count_leading_identical(1i128), 127);
assert_eq!(count_leading_identical(-2i128), 126);
}
#[test]
fn test_lattice_undefined() {
let u = X86ValueLatticeElement::Undefined;
assert!(u.is_undefined());
assert!(!u.is_overdefined());
}
#[test]
fn test_lattice_constant() {
let c = X86ValueLatticeElement::Constant(42);
assert!(c.is_constant());
assert_eq!(c.as_constant(), Some(42));
assert!(c.contains(42));
assert!(!c.contains(0));
}
#[test]
fn test_lattice_meet_constants_same() {
assert_eq!(
X86ValueLatticeElement::Constant(5)
.meet(&X86ValueLatticeElement::Constant(5))
.as_constant(),
Some(5)
);
}
#[test]
fn test_lattice_meet_constants_diff() {
assert!(X86ValueLatticeElement::Constant(5)
.meet(&X86ValueLatticeElement::Constant(10))
.is_overdefined());
}
#[test]
fn test_lattice_meet_undef() {
assert_eq!(
X86ValueLatticeElement::Undefined
.meet(&X86ValueLatticeElement::Constant(7))
.as_constant(),
Some(7)
);
}
#[test]
fn test_lattice_meet_overdefined() {
assert!(X86ValueLatticeElement::Overdefined
.meet(&X86ValueLatticeElement::Constant(7))
.is_overdefined());
}
#[test]
fn test_lattice_meet_ranges() {
let a = X86ValueLatticeElement::ConstantRange {
lo: 0,
hi: 10,
is_signed: false,
};
let b = X86ValueLatticeElement::ConstantRange {
lo: 5,
hi: 15,
is_signed: false,
};
match a.meet(&b) {
X86ValueLatticeElement::ConstantRange { lo, hi, .. } => {
assert_eq!(lo, 5);
assert_eq!(hi, 10);
}
_ => panic!("Expected ConstantRange"),
}
}
#[test]
fn test_lattice_join_constants() {
match X86ValueLatticeElement::Constant(3).join(&X86ValueLatticeElement::Constant(7)) {
X86ValueLatticeElement::ConstantRange { lo, hi, .. } => {
assert_eq!(lo, 3);
assert_eq!(hi, 7);
}
_ => panic!("Expected ConstantRange"),
}
}
#[test]
fn test_edge_constraint_eq() {
let c = X86EdgeConstraint::Eq {
value_vid: 1,
constant: 5,
};
let l = X86ValueLatticeElement::ConstantRange {
lo: 0,
hi: 10,
is_signed: false,
};
assert_eq!(c.apply_to(&l).as_constant(), Some(5));
}
#[test]
fn test_edge_constraint_ult() {
let c = X86EdgeConstraint::Ult {
value_vid: 1,
constant: 5,
};
let l = X86ValueLatticeElement::ConstantRange {
lo: 0,
hi: 10,
is_signed: false,
};
match c.apply_to(&l) {
X86ValueLatticeElement::ConstantRange { lo, hi, .. } => {
assert_eq!(lo, 0);
assert_eq!(hi, 4);
}
_ => panic!("Expected ConstantRange"),
}
}
#[test]
fn test_lvi_new() {
let lvi = X86LazyValueInfo::new();
assert_eq!(lvi.num_queries, 0);
assert_eq!(lvi.num_cache_hits, 0);
}
#[test]
fn test_lvi_set_global_constant() {
let mut lvi = X86LazyValueInfo::new();
lvi.set_global_constant(100, 42);
let val = make_const_i32(100, 42, "");
let block = make_dummy_ref(1, Opcode::Br);
assert_eq!(lvi.get_value_at_block(&val, &block).as_constant(), Some(42));
}
#[test]
fn test_lvi_evaluate_icmp_eq_true() {
assert_eq!(
X86LazyValueInfo::evaluate_icmp_lattice(
ICmpPred::Eq,
&X86ValueLatticeElement::Constant(5),
&X86ValueLatticeElement::Constant(5)
),
Some(true)
);
}
#[test]
fn test_lvi_evaluate_icmp_eq_false() {
assert_eq!(
X86LazyValueInfo::evaluate_icmp_lattice(
ICmpPred::Eq,
&X86ValueLatticeElement::Constant(5),
&X86ValueLatticeElement::Constant(3)
),
Some(false)
);
}
#[test]
fn test_lvi_evaluate_icmp_ugt_range() {
let l = X86ValueLatticeElement::ConstantRange {
lo: 10,
hi: 20,
is_signed: false,
};
let r = X86ValueLatticeElement::ConstantRange {
lo: 0,
hi: 5,
is_signed: false,
};
assert_eq!(
X86LazyValueInfo::evaluate_icmp_lattice(ICmpPred::Ugt, &l, &r),
Some(true)
);
}
#[test]
fn test_lattice_add() {
assert_eq!(
X86LazyValueInfo::compute_add_lattice(
&X86ValueLatticeElement::Constant(5),
&X86ValueLatticeElement::Constant(3)
)
.as_constant(),
Some(8)
);
}
#[test]
fn test_lattice_mul() {
assert_eq!(
X86LazyValueInfo::compute_mul_lattice(
&X86ValueLatticeElement::Constant(6),
&X86ValueLatticeElement::Constant(7)
)
.as_constant(),
Some(42)
);
}
#[test]
fn test_lvi_pred_to_constraint() {
let lvi = X86LazyValueInfo::new();
let (tc, fc) = lvi.pred_to_constraint(ICmpPred::Eq, 10, 5);
assert_eq!(
tc,
X86EdgeConstraint::Eq {
value_vid: 10,
constant: 5
}
);
assert_eq!(
fc,
X86EdgeConstraint::Ne {
value_vid: 10,
constant: 5
}
);
}
#[test]
fn test_demanded_bits_new() {
let db = X86DemandedBits::default_for_x86_64();
assert_eq!(db.max_bit_width, 64);
}
#[test]
fn test_demanded_bits_default() {
let db = X86DemandedBits::default_for_x86_64();
let val = make_const_i32(600, 42, "");
assert_eq!(db.get_demanded_bits(&val), !0u128);
}
#[test]
fn test_demanded_bits_set_get() {
let mut db = X86DemandedBits::default_for_x86_64();
let val = make_const_i32(700, 42, "");
db.set_demanded_bits(&val, 0xFF);
assert_eq!(db.get_demanded_bits(&val), 0xFF);
}
#[test]
fn test_demanded_bits_low_true() {
let mut db = X86DemandedBits::default_for_x86_64();
let val = make_const_i32(800, 42, "");
db.set_demanded_bits(&val, 0x0F);
db.set_bit_width(&val, 32);
assert!(db.is_only_demanded_low_bits(&val, 8));
}
#[test]
fn test_demanded_bits_low_false() {
let mut db = X86DemandedBits::default_for_x86_64();
let val = make_const_i32(900, 42, "");
db.set_demanded_bits(&val, 0x100);
db.set_bit_width(&val, 32);
assert!(!db.is_only_demanded_low_bits(&val, 8));
}
#[test]
fn test_demanded_bits_count() {
let mut db = X86DemandedBits::default_for_x86_64();
let val = make_const_i32(1000, 42, "");
db.set_demanded_bits(&val, 0x0F);
assert_eq!(db.count_demanded_bits(&val), 4);
}
#[test]
fn test_demanded_bits_clear() {
let mut db = X86DemandedBits::default_for_x86_64();
let val = make_const_i32(1100, 42, "");
db.set_demanded_bits(&val, 0xFF);
db.clear();
assert_eq!(db.get_demanded_bits(&val), !0u128);
}
#[test]
fn test_mapper_new() {
let m = X86ValueMapper::new();
assert_eq!(m.num_mapped_values(), 0);
assert_eq!(m.num_equivalence_classes(), 0);
}
#[test]
fn test_mapper_register_map() {
let mut m = X86ValueMapper::new();
m.register_value(10, 20);
assert_eq!(m.map_value(10), 20);
assert_eq!(m.map_value(30), 30);
}
#[test]
fn test_mapper_type() {
let mut m = X86ValueMapper::new();
m.register_type(1, 100);
assert_eq!(m.map_type(1), 100);
}
#[test]
fn test_mapper_equivalence() {
let mut m = X86ValueMapper::new();
m.create_equivalence_class(1);
m.add_to_equivalence_class(1, 2);
m.add_to_equivalence_class(1, 3);
assert_eq!(m.get_canonical(2), 1);
assert!(m.are_equivalent(2, 3));
assert!(!m.are_equivalent(2, 4));
}
#[test]
fn test_mapper_get_equiv() {
let mut m = X86ValueMapper::new();
m.create_equivalence_class(10);
m.add_to_equivalence_class(10, 20);
m.add_to_equivalence_class(10, 30);
let e = m.get_equivalent_values(20);
assert!(e.contains(&10) && e.contains(&20) && e.contains(&30));
}
#[test]
fn test_mapper_cse() {
let mut m = X86ValueMapper::new();
let ops = vec![1, 2, 3];
assert_eq!(m.try_cse(Opcode::Add, &ops), None);
m.record_cse(Opcode::Add, &ops, 42);
assert_eq!(m.try_cse(Opcode::Add, &ops), Some(42));
}
#[test]
fn test_mapper_block() {
let mut m = X86ValueMapper::new();
m.register_block(5, 15);
assert_eq!(m.map_block(5), 15);
}
#[test]
fn test_mapper_allocate() {
let mut m = X86ValueMapper::new();
let a = m.allocate_vid();
let b = m.allocate_vid();
assert!(b > a);
}
#[test]
fn test_vt_new() {
let vt = X86ValueTracking::new();
assert!(!vt.analyzed);
}
#[test]
fn test_vt_default() {
let vt = X86ValueTracking::default();
assert!(!vt.analyzed);
}
#[test]
fn test_vt_unknown_kb() {
let vt = X86ValueTracking::new();
let val = make_dummy_ref(5000, Opcode::Add);
assert_eq!(vt.get_known_bits(&val), X86KnownBits::unknown());
}
#[test]
fn test_vt_unknown_constant() {
let vt = X86ValueTracking::new();
let val = make_dummy_ref(5100, Opcode::Add);
assert_eq!(vt.is_known_constant(&val), None);
}
#[test]
fn test_vt_clear() {
let mut vt = X86ValueTracking::new();
vt.analyzed = true;
vt.clear();
assert!(!vt.analyzed);
}
#[test]
fn test_vt_stats_default() {
let s = X86ValueTrackingStats::default();
assert_eq!(s.num_values_analyzed, 0);
assert_eq!(s.num_known_bits_computed, 0);
}
#[test]
fn test_pipeline_and_constants() {
let mut vt = X86ValueTracking::new();
let a = make_const_i32(40000, 0xFF, "");
let b = make_const_i32(50000, 0x0F, "");
{
let mut a_mut = a.borrow_mut();
a_mut.opcode = Some(Opcode::And);
a_mut.operands = vec![a.clone(), b.clone()];
}
vt.compute_known_bits_for_value(&a);
}
#[test]
fn test_helpers() {
let c = make_const_i64(99999, 12345);
assert_eq!(get_constant_u64(&c), Some(12345));
let n = make_const_i64(88888, -42);
assert_eq!(get_constant_i64(&n), Some(-42));
}
#[test]
fn test_type_bit_width() {
assert_eq!(type_bit_width(&Type::Int32), 32);
assert_eq!(type_bit_width(&Type::Int64), 64);
assert_eq!(type_bit_width(&Type::Pointer(Box::new(Type::Int8))), 64);
}
#[test]
fn test_known_bits_conflict_resolution() {
let kb = X86KnownBits::new(0x03, 0x03);
assert_eq!(kb.zero & kb.one, 0);
}
#[test]
fn test_edge_constraint_ne_overdefined() {
let c = X86EdgeConstraint::Ne {
value_vid: 1,
constant: 5,
};
let l = X86ValueLatticeElement::Constant(5);
assert!(c.apply_to(&l).is_overdefined());
}
#[test]
fn test_edge_constraint_ne_passes() {
let c = X86EdgeConstraint::Ne {
value_vid: 1,
constant: 5,
};
let l = X86ValueLatticeElement::Constant(3);
assert_eq!(c.apply_to(&l).as_constant(), Some(3));
}
#[test]
fn test_lattice_meet_to_singleton() {
let a = X86ValueLatticeElement::ConstantRange {
lo: 5,
hi: 10,
is_signed: false,
};
let b = X86ValueLatticeElement::ConstantRange {
lo: 7,
hi: 7,
is_signed: false,
};
assert_eq!(a.meet(&b).as_constant(), Some(7));
}
#[test]
fn test_known_bits_not() {
let a = X86KnownBits::constant(0x0F);
assert_eq!(
X86KnownBits::compute_not(&a).get_constant(),
Some(!0x0Fu128)
);
}
#[test]
fn test_mapper_equivalence_transitive() {
let mut m = X86ValueMapper::new();
m.create_equivalence_class(1);
m.add_to_equivalence_class(1, 2);
m.add_to_equivalence_class(2, 3);
assert!(m.are_equivalent(1, 3));
}
#[test]
fn test_demanded_bit_width_shrink() {
let mut db = X86DemandedBits::default_for_x86_64();
let val = make_dummy_ref(50000, Opcode::Add);
db.set_demanded_bits(&val, 0xFF);
db.set_bit_width(&val, 64);
let shrink = db.get_demanded_bit_width(&val);
assert!(shrink.is_some());
assert!(shrink.unwrap() <= 32); }
}