use std::cmp::min;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
use std::fmt;
use std::rc::Rc;
const X86_CFL_MAX_NODES_SPARSE: usize = 4096;
const X86_ALIAS_MAX_RECURSION_DEPTH: usize = 20;
const X86_MAX_POINTS_TO_SET: usize = 512;
const X86_MAX_ALIAS_SETS: usize = 1024;
const X86_SCEV_MAX_BACKEDGE_TAKEN_COUNT: u64 = 1_000_000;
const X86_MEMSSA_MAX_WALK_DEPTH: usize = 100;
const X86_BATCH_QUERY_THRESHOLD: usize = 32;
const X86_MEMDEP_CACHE_SIZE: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86Opcode {
Ret,
Br,
Switch,
IndirectBr,
Invoke,
CallBr,
Unreachable,
Alloca,
Load,
Store,
GetElementPtr,
Fence,
AtomicCmpXchg,
AtomicRMW,
BitCast,
IntToPtr,
PtrToInt,
Trunc,
ZExt,
SExt,
FPTrunc,
FPExt,
FPToUI,
FPToSI,
UIToFP,
SIToFP,
AddrSpaceCast,
Add,
Sub,
Mul,
UDiv,
SDiv,
URem,
SRem,
And,
Or,
Xor,
Shl,
LShr,
AShr,
Phi,
Select,
Call,
ExtractValue,
InsertValue,
ExtractElement,
InsertElement,
ShuffleVector,
Unknown,
}
impl fmt::Display for X86Opcode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AliasResult {
NoAlias = 0,
MayAlias = 1,
PartialAlias = 2,
MustAlias = 3,
}
impl fmt::Display for AliasResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AliasResult::NoAlias => write!(f, "NoAlias"),
AliasResult::MayAlias => write!(f, "MayAlias"),
AliasResult::PartialAlias => write!(f, "PartialAlias"),
AliasResult::MustAlias => write!(f, "MustAlias"),
}
}
}
impl AliasResult {
pub fn meet(a: AliasResult, b: AliasResult) -> AliasResult {
use AliasResult::*;
match (a, b) {
(NoAlias, _) => NoAlias,
(_, NoAlias) => NoAlias,
(MustAlias, _) => b,
(_, MustAlias) => a,
(PartialAlias, PartialAlias) => PartialAlias,
(MayAlias, _) | (_, MayAlias) => MayAlias,
_ => MayAlias,
}
}
pub fn top() -> Self {
AliasResult::MayAlias
}
pub fn bottom() -> Self {
AliasResult::NoAlias
}
pub fn is_no_alias(&self) -> bool {
matches!(self, AliasResult::NoAlias)
}
pub fn is_must_alias(&self) -> bool {
matches!(self, AliasResult::MustAlias)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ModRefInfo {
NoModRef = 0,
Ref = 1,
Mod = 2,
ModRef = 3,
}
impl fmt::Display for ModRefInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ModRefInfo::NoModRef => write!(f, "NoModRef"),
ModRefInfo::Ref => write!(f, "Ref"),
ModRefInfo::Mod => write!(f, "Mod"),
ModRefInfo::ModRef => write!(f, "ModRef"),
}
}
}
impl ModRefInfo {
pub fn does_ref(&self) -> bool {
matches!(self, ModRefInfo::Ref | ModRefInfo::ModRef)
}
pub fn does_mod(&self) -> bool {
matches!(self, ModRefInfo::Mod | ModRefInfo::ModRef)
}
pub fn union(a: ModRefInfo, b: ModRefInfo) -> ModRefInfo {
use ModRefInfo::*;
match (a, b) {
(x, NoModRef) | (NoModRef, x) => x,
(Ref, Ref) => Ref,
(Mod, Mod) => Mod,
_ => ModRef,
}
}
pub fn meet(a: ModRefInfo, b: ModRefInfo) -> ModRefInfo {
use ModRefInfo::*;
match (a, b) {
(ModRef, x) | (x, ModRef) => x,
(NoModRef, _) | (_, NoModRef) => NoModRef,
(Ref, Mod) | (Mod, Ref) => NoModRef,
_ => a,
}
}
pub fn top() -> Self {
ModRefInfo::ModRef
}
pub fn bottom() -> Self {
ModRefInfo::NoModRef
}
pub fn clear_mod(&mut self) {
*self = match *self {
ModRefInfo::ModRef => ModRefInfo::Ref,
ModRefInfo::Mod => ModRefInfo::NoModRef,
other => other,
};
}
pub fn clear_ref(&mut self) {
*self = match *self {
ModRefInfo::ModRef => ModRefInfo::Mod,
ModRefInfo::Ref => ModRefInfo::NoModRef,
other => other,
};
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MemoryLocation {
pub ptr: u64,
pub size: u64,
pub tbaa_tag: Option<String>,
pub noalias_domain: Option<String>,
pub noalias_scopes: Vec<String>,
pub has_known_size: bool,
}
impl MemoryLocation {
pub fn new(ptr: u64, size: u64) -> Self {
MemoryLocation {
ptr,
size,
tbaa_tag: None,
noalias_domain: None,
noalias_scopes: Vec::new(),
has_known_size: size > 0,
}
}
pub fn with_tbaa(mut self, tag: String) -> Self {
self.tbaa_tag = Some(tag);
self
}
pub fn with_scoped_noalias(mut self, domain: String, scopes: Vec<String>) -> Self {
self.noalias_domain = Some(domain);
self.noalias_scopes = scopes;
self
}
pub fn has_tbaa(&self) -> bool {
self.tbaa_tag.is_some()
}
pub fn has_scoped_noalias(&self) -> bool {
self.noalias_domain.is_some() && !self.noalias_scopes.is_empty()
}
pub fn is_unknown_size(&self) -> bool {
!self.has_known_size
}
}
#[derive(Debug, Clone, Default)]
pub struct PointsToSet {
pub objects: HashSet<u64>,
pub includes_null: bool,
pub includes_unknown: bool,
pub is_noalias: bool,
}
impl PointsToSet {
pub fn empty() -> Self {
PointsToSet {
objects: HashSet::new(),
includes_null: false,
includes_unknown: false,
is_noalias: false,
}
}
pub fn unknown() -> Self {
PointsToSet {
objects: HashSet::new(),
includes_null: true,
includes_unknown: true,
is_noalias: false,
}
}
pub fn singleton(obj: u64) -> Self {
let mut objects = HashSet::new();
objects.insert(obj);
PointsToSet {
objects,
includes_null: false,
includes_unknown: false,
is_noalias: false,
}
}
pub fn noalias() -> Self {
PointsToSet {
objects: HashSet::new(),
includes_null: false,
includes_unknown: false,
is_noalias: true,
}
}
pub fn union_with(&mut self, other: &PointsToSet) {
if other.is_noalias {
return;
}
if self.is_noalias {
self.is_noalias = false;
}
self.includes_null |= other.includes_null;
self.includes_unknown |= other.includes_unknown;
if self.objects.len() + other.objects.len() <= X86_MAX_POINTS_TO_SET {
self.objects.extend(&other.objects);
} else {
self.includes_unknown = true;
self.objects.clear();
}
}
pub fn may_alias(&self, other: &PointsToSet) -> bool {
if self.is_noalias || other.is_noalias {
return false;
}
if self.includes_unknown || other.includes_unknown {
return true;
}
if self.includes_null && other.includes_null {
return true;
}
!self.objects.is_disjoint(&other.objects)
}
pub fn len(&self) -> usize {
self.objects.len()
}
pub fn is_empty(&self) -> bool {
self.objects.is_empty() && !self.includes_unknown && !self.includes_null
}
}
#[derive(Debug, Clone)]
pub struct AliasSet {
pub id: u64,
pub pointers: HashSet<u64>,
pub has_unknown: bool,
pub access_type: AliasSetAccess,
pub is_volatile: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AliasSetAccess {
Ref,
Mod,
ModRef,
}
impl AliasSet {
pub fn new(id: u64) -> Self {
AliasSet {
id,
pointers: HashSet::new(),
has_unknown: false,
access_type: AliasSetAccess::Ref,
is_volatile: false,
}
}
pub fn add_pointer(&mut self, ptr: u64) {
if self.pointers.len() < X86_MAX_ALIAS_SETS {
self.pointers.insert(ptr);
} else {
self.has_unknown = true;
}
}
pub fn merge(&mut self, other: &AliasSet) {
self.pointers.extend(&other.pointers);
self.has_unknown |= other.has_unknown;
self.is_volatile |= other.is_volatile;
self.access_type = match (self.access_type, other.access_type) {
(AliasSetAccess::Ref, AliasSetAccess::Ref) => AliasSetAccess::Ref,
(AliasSetAccess::Mod, AliasSetAccess::Mod) => AliasSetAccess::Mod,
_ => AliasSetAccess::ModRef,
};
}
}
#[derive(Debug, Clone)]
pub struct X86AliasResult {
pub result: AliasResult,
pub source: String,
pub explanation: Option<String>,
}
impl X86AliasResult {
pub fn new(result: AliasResult, source: &str) -> Self {
X86AliasResult {
result,
source: source.to_string(),
explanation: None,
}
}
pub fn with_explanation(mut self, explanation: &str) -> Self {
self.explanation = Some(explanation.to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct X86BasicAA {
pub alloca_sizes: HashMap<u64, u64>,
pub global_sizes: HashMap<u64, u64>,
pub global_constants: HashSet<u64>,
pub function_addresses: HashSet<u64>,
pub gep_bases: HashMap<u64, u64>,
pub gep_offsets: HashMap<u64, (u64, i64)>,
pub stats: X86BasicAAStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86BasicAAStats {
pub total_queries: u64,
pub noalias_results: u64,
pub mustalias_results: u64,
pub partialalias_results: u64,
pub mayalias_results: u64,
pub gep_disambiguations: u64,
pub type_disambiguations: u64,
}
impl X86BasicAA {
pub fn new() -> Self {
X86BasicAA {
alloca_sizes: HashMap::new(),
global_sizes: HashMap::new(),
global_constants: HashSet::new(),
function_addresses: HashSet::new(),
gep_bases: HashMap::new(),
gep_offsets: HashMap::new(),
stats: X86BasicAAStats::default(),
}
}
pub fn register_alloca(&mut self, ptr: u64, size: u64) {
self.alloca_sizes.insert(ptr, size);
}
pub fn register_global(&mut self, ptr: u64, size: u64, is_constant: bool) {
self.global_sizes.insert(ptr, size);
if is_constant {
self.global_constants.insert(ptr);
}
}
pub fn register_gep(&mut self, gep_ptr: u64, base: u64, offset: i64) {
self.gep_bases.insert(gep_ptr, base);
self.gep_offsets.insert(gep_ptr, (base, offset));
}
pub fn alias(&mut self, loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> AliasResult {
self.stats.total_queries += 1;
if loc_a.ptr == loc_b.ptr && loc_a.ptr != 0 {
if loc_a.size == loc_b.size || loc_a.size == 0 || loc_b.size == 0 {
self.stats.mustalias_results += 1;
return AliasResult::MustAlias;
}
self.stats.partialalias_results += 1;
return AliasResult::PartialAlias;
}
if loc_a.ptr == 0 || loc_b.ptr == 0 {
if loc_a.ptr != loc_b.ptr {
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
return AliasResult::MayAlias;
}
if self.alloca_sizes.contains_key(&loc_a.ptr)
&& self.alloca_sizes.contains_key(&loc_b.ptr)
&& loc_a.ptr != loc_b.ptr
{
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
if self.global_sizes.contains_key(&loc_a.ptr)
&& self.global_sizes.contains_key(&loc_b.ptr)
&& loc_a.ptr != loc_b.ptr
{
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
if (self.alloca_sizes.contains_key(&loc_a.ptr)
&& self.global_sizes.contains_key(&loc_b.ptr))
|| (self.alloca_sizes.contains_key(&loc_b.ptr)
&& self.global_sizes.contains_key(&loc_a.ptr))
{
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
let base_a = self.resolve_gep_base(loc_a.ptr);
let base_b = self.resolve_gep_base(loc_b.ptr);
let offset_a = self.resolve_gep_offset(loc_a.ptr);
let offset_b = self.resolve_gep_offset(loc_b.ptr);
if let (Some(ba), Some(bb)) = (base_a, base_b) {
if ba != bb {
self.stats.gep_disambiguations += 1;
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
if let (Some(oa), Some(ob)) = (offset_a, offset_b) {
let size_a = loc_a.size as i64;
let size_b = loc_b.size as i64;
if oa == ob && size_a == size_b {
self.stats.mustalias_results += 1;
return AliasResult::MustAlias;
}
if size_a > 0 && size_b > 0 {
let end_a = oa + size_a;
let end_b = ob + size_b;
if end_a <= ob || end_b <= oa {
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
self.stats.partialalias_results += 1;
return AliasResult::PartialAlias;
}
}
}
if self.function_addresses.contains(&loc_a.ptr)
!= self.function_addresses.contains(&loc_b.ptr)
{
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
self.stats.mayalias_results += 1;
AliasResult::MayAlias
}
fn resolve_gep_base(&self, ptr: u64) -> Option<u64> {
let mut current = ptr;
let mut visited = HashSet::new();
for _ in 0..X86_ALIAS_MAX_RECURSION_DEPTH {
if visited.contains(¤t) {
break;
}
visited.insert(current);
if self.alloca_sizes.contains_key(¤t) || self.global_sizes.contains_key(¤t)
{
return Some(current);
}
match self.gep_bases.get(¤t) {
Some(base) => {
current = *base;
}
None => break,
}
}
None
}
fn resolve_gep_offset(&self, ptr: u64) -> Option<i64> {
self.gep_offsets.get(&ptr).map(|(_base, offset)| *offset)
}
pub fn points_to_constant_memory(&self, loc: &MemoryLocation) -> bool {
let base = self.resolve_gep_base(loc.ptr).unwrap_or(loc.ptr);
self.global_constants.contains(&base)
}
pub fn get_object_size(&self, ptr: u64) -> Option<u64> {
let base = self.resolve_gep_base(ptr).unwrap_or(ptr);
self.alloca_sizes
.get(&base)
.copied()
.or_else(|| self.global_sizes.get(&base).copied())
}
pub fn get_stats(&self) -> &X86BasicAAStats {
&self.stats
}
}
impl Default for X86BasicAA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CFLAA {
pub points_to: HashMap<u64, PointsToSet>,
pub assign_edges: Vec<(u64, u64)>,
pub load_edges: Vec<(u64, u64)>,
pub store_edges: Vec<(u64, u64)>,
rev_assign: HashMap<u64, Vec<u64>>,
deref_cache: HashMap<u64, PointsToSet>,
worklist: VecDeque<(u64, u64)>,
pub solved: bool,
pub stats: X86CFLAAStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86CFLAAStats {
pub total_nodes: usize,
pub total_edges: usize,
pub assign_edges: usize,
pub load_edges: usize,
pub store_edges: usize,
pub iterations_to_fixpoint: u64,
pub queries_resolved: u64,
pub noalias_results: u64,
}
impl X86CFLAA {
pub fn new() -> Self {
X86CFLAA {
points_to: HashMap::new(),
assign_edges: Vec::new(),
load_edges: Vec::new(),
store_edges: Vec::new(),
rev_assign: HashMap::new(),
deref_cache: HashMap::new(),
worklist: VecDeque::new(),
solved: false,
stats: X86CFLAAStats::default(),
}
}
pub fn register_pointer(&mut self, ptr: u64) {
self.points_to.entry(ptr).or_insert_with(PointsToSet::empty);
}
pub fn add_assign(&mut self, lhs: u64, rhs: u64) {
self.assign_edges.push((lhs, rhs));
self.stats.assign_edges += 1;
}
pub fn add_load(&mut self, lhs: u64, rhs: u64) {
self.load_edges.push((lhs, rhs));
self.stats.load_edges += 1;
}
pub fn add_store(&mut self, lhs: u64, rhs: u64) {
self.store_edges.push((lhs, rhs));
self.stats.store_edges += 1;
}
pub fn add_points_to(&mut self, ptr: u64, obj: u64) {
let entry = self.points_to.entry(ptr).or_insert_with(PointsToSet::empty);
entry.objects.insert(obj);
}
pub fn mark_escaped(&mut self, ptr: u64) {
let entry = self.points_to.entry(ptr).or_insert_with(PointsToSet::empty);
entry.includes_unknown = true;
}
pub fn solve(&mut self) {
if self.solved {
return;
}
self.stats.total_nodes = self.points_to.len();
self.stats.total_edges =
self.assign_edges.len() + self.load_edges.len() + self.store_edges.len();
self.rev_assign.clear();
for &(from, to) in &self.assign_edges {
self.rev_assign.entry(to).or_default().push(from);
}
self.worklist.clear();
for &(lhs, rhs) in &self.assign_edges {
self.worklist.push_back((lhs, rhs));
}
let mut iteration = 0u64;
loop {
if self.worklist.is_empty() {
break;
}
iteration += 1;
if iteration > 1_000_000 {
break;
}
let batch_size = min(self.worklist.len(), 64);
for _ in 0..batch_size {
if let Some((lhs, rhs)) = self.worklist.pop_front() {
self.process_worklist_item(lhs, rhs);
}
}
}
self.stats.iterations_to_fixpoint = iteration;
self.propagate_loads();
self.deref_cache.clear();
self.solved = true;
}
fn process_worklist_item(&mut self, lhs: u64, rhs: u64) {
let rhs_pts = self
.points_to
.get(&rhs)
.cloned()
.unwrap_or_else(PointsToSet::empty);
let lhs_entry = self.points_to.entry(lhs).or_insert_with(PointsToSet::empty);
let old_len = lhs_entry.objects.len();
let old_unknown = lhs_entry.includes_unknown;
let old_null = lhs_entry.includes_null;
lhs_entry.union_with(&rhs_pts);
if lhs_entry.objects.len() != old_len
|| lhs_entry.includes_unknown != old_unknown
|| lhs_entry.includes_null != old_null
{
if let Some(rev_list) = self.rev_assign.get(&lhs).cloned() {
for &target in &rev_list {
self.worklist.push_back((target, lhs));
}
}
for &(store_base, store_val) in &self.store_edges {
if store_base == lhs {
let store_entry = self
.points_to
.entry(store_val)
.or_insert_with(PointsToSet::empty);
store_entry.union_with(&rhs_pts);
}
}
}
}
fn propagate_loads(&mut self) {
let load_edges: Vec<(u64, u64)> = self.load_edges.iter().copied().collect();
for (load_target, load_base) in load_edges {
let deref_pts = self.compute_deref(load_base);
let target_entry = self
.points_to
.entry(load_target)
.or_insert_with(PointsToSet::empty);
target_entry.union_with(&deref_pts);
}
}
fn compute_deref(&mut self, ptr: u64) -> PointsToSet {
if let Some(cached) = self.deref_cache.get(&ptr) {
return cached.clone();
}
let pts = self
.points_to
.get(&ptr)
.cloned()
.unwrap_or_else(PointsToSet::empty);
let mut result = PointsToSet::empty();
if pts.includes_unknown {
result.includes_unknown = true;
}
for &obj in &pts.objects {
if let Some(obj_pts) = self.points_to.get(&obj) {
result.union_with(obj_pts);
}
}
self.deref_cache.insert(ptr, result.clone());
result
}
pub fn may_alias(&mut self, ptr_a: u64, ptr_b: u64) -> AliasResult {
if !self.solved {
self.solve();
}
self.stats.queries_resolved += 1;
if ptr_a == ptr_b {
if ptr_a != 0 {
return AliasResult::MustAlias;
}
return AliasResult::NoAlias;
}
let pts_a = self
.points_to
.get(&ptr_a)
.cloned()
.unwrap_or_else(PointsToSet::empty);
let pts_b = self
.points_to
.get(&ptr_b)
.cloned()
.unwrap_or_else(PointsToSet::empty);
if pts_a.is_noalias || pts_b.is_noalias {
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
if pts_a.may_alias(&pts_b) {
AliasResult::MayAlias
} else {
self.stats.noalias_results += 1;
AliasResult::NoAlias
}
}
pub fn get_points_to(&self, ptr: u64) -> PointsToSet {
self.points_to
.get(&ptr)
.cloned()
.unwrap_or_else(PointsToSet::empty)
}
pub fn must_alias(&self, ptr_a: u64, ptr_b: u64) -> bool {
let pts_a = self.get_points_to(ptr_a);
let pts_b = self.get_points_to(ptr_b);
if pts_a.objects.len() == 1
&& pts_b.objects.len() == 1
&& !pts_a.includes_unknown
&& !pts_b.includes_unknown
{
pts_a.objects == pts_b.objects
} else {
false
}
}
}
impl Default for X86CFLAA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum X86SCEVExpr {
Constant(i64),
Unknown,
AddRec {
base: Box<X86SCEVExpr>,
step: Box<X86SCEVExpr>,
loop_id: u64,
},
Add(Box<X86SCEVExpr>, Box<X86SCEVExpr>),
Mul(Box<X86SCEVExpr>, Box<X86SCEVExpr>),
SCEVValue(u64),
ZExt(Box<X86SCEVExpr>),
SExt(Box<X86SCEVExpr>),
Truncate(Box<X86SCEVExpr>),
CouldNotCompute,
}
impl fmt::Display for X86SCEVExpr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86SCEVExpr::Constant(v) => write!(f, "{}", v),
X86SCEVExpr::Unknown => write!(f, "unknown"),
X86SCEVExpr::CouldNotCompute => write!(f, "couldnotcompute"),
X86SCEVExpr::AddRec {
base,
step,
loop_id,
} => {
write!(f, "{{{},+,{}}}<loop_{}>", base, step, loop_id)
}
X86SCEVExpr::Add(a, b) => write!(f, "({} + {})", a, b),
X86SCEVExpr::Mul(a, b) => write!(f, "({} * {})", a, b),
X86SCEVExpr::SCEVValue(v) => write!(f, "v{}", v),
X86SCEVExpr::ZExt(e) => write!(f, "(zext {})", e),
X86SCEVExpr::SExt(e) => write!(f, "(sext {})", e),
X86SCEVExpr::Truncate(e) => write!(f, "(trunc {})", e),
}
}
}
impl X86SCEVExpr {
pub fn constant(v: i64) -> Self {
X86SCEVExpr::Constant(v)
}
pub fn unknown() -> Self {
X86SCEVExpr::Unknown
}
pub fn addrec(base: X86SCEVExpr, step: X86SCEVExpr, loop_id: u64) -> Self {
X86SCEVExpr::AddRec {
base: Box::new(base),
step: Box::new(step),
loop_id,
}
}
pub fn evaluate_at(&self, iteration: i64) -> Option<i64> {
match self {
X86SCEVExpr::Constant(v) => Some(*v),
X86SCEVExpr::AddRec { base, step, .. } => {
let b = base.evaluate_at(iteration)?;
let s = step.evaluate_at(iteration)?;
Some(b + s * iteration)
}
X86SCEVExpr::Add(a, b) => {
let va = a.evaluate_at(iteration)?;
let vb = b.evaluate_at(iteration)?;
Some(va + vb)
}
X86SCEVExpr::Mul(a, b) => {
let va = a.evaluate_at(iteration)?;
let vb = b.evaluate_at(iteration)?;
Some(va * vb)
}
X86SCEVExpr::ZExt(e) | X86SCEVExpr::SExt(e) | X86SCEVExpr::Truncate(e) => {
e.evaluate_at(iteration)
}
X86SCEVExpr::SCEVValue(_) | X86SCEVExpr::Unknown | X86SCEVExpr::CouldNotCompute => None,
}
}
pub fn is_constant(&self) -> bool {
matches!(self, X86SCEVExpr::Constant(_))
}
pub fn is_could_not_compute(&self) -> bool {
matches!(self, X86SCEVExpr::CouldNotCompute)
}
pub fn is_loop_invariant(&self, loop_id: u64) -> bool {
match self {
X86SCEVExpr::Constant(_) => true,
X86SCEVExpr::AddRec { loop_id: lid, .. } => *lid != loop_id,
X86SCEVExpr::Add(a, b) => a.is_loop_invariant(loop_id) && b.is_loop_invariant(loop_id),
X86SCEVExpr::Mul(a, b) => a.is_loop_invariant(loop_id) && b.is_loop_invariant(loop_id),
X86SCEVExpr::ZExt(e) | X86SCEVExpr::SExt(e) | X86SCEVExpr::Truncate(e) => {
e.is_loop_invariant(loop_id)
}
_ => false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86SCEVAA {
pub scev_map: HashMap<u64, X86SCEVExpr>,
pub loop_bounds: HashMap<u64, u64>,
pub stats: X86SCEVAAStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86SCEVAAStats {
pub total_queries: u64,
pub noalias_results: u64,
pub scev_disambiguations: u64,
}
impl X86SCEVAA {
pub fn new() -> Self {
X86SCEVAA {
scev_map: HashMap::new(),
loop_bounds: HashMap::new(),
stats: X86SCEVAAStats::default(),
}
}
pub fn register_scev(&mut self, value: u64, expr: X86SCEVExpr) {
self.scev_map.insert(value, expr);
}
pub fn register_loop_bound(&mut self, loop_id: u64, max_count: u64) {
self.loop_bounds.insert(loop_id, max_count);
}
pub fn alias(&mut self, loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> AliasResult {
self.stats.total_queries += 1;
let scev_a = self.scev_map.get(&loc_a.ptr);
let scev_b = self.scev_map.get(&loc_b.ptr);
match (scev_a, scev_b) {
(Some(a), Some(b)) => {
if self.scev_prove_noalias(a, b, loc_a.size, loc_b.size) {
self.stats.scev_disambiguations += 1;
self.stats.noalias_results += 1;
return AliasResult::NoAlias;
}
if self.scev_prove_mustalias(a, b) {
return AliasResult::MustAlias;
}
}
_ => {}
}
AliasResult::MayAlias
}
fn scev_prove_noalias(
&self,
a: &X86SCEVExpr,
b: &X86SCEVExpr,
size_a: u64,
size_b: u64,
) -> bool {
if let (
X86SCEVExpr::AddRec {
base: base_a,
step: step_a,
loop_id: lid_a,
},
X86SCEVExpr::AddRec {
base: base_b,
step: step_b,
loop_id: lid_b,
},
) = (a, b)
{
if lid_a == lid_b {
if let (X86SCEVExpr::Constant(base_a_val), X86SCEVExpr::Constant(base_b_val)) =
(base_a.as_ref(), base_b.as_ref())
{
if let (X86SCEVExpr::Constant(step_a_val), X86SCEVExpr::Constant(step_b_val)) =
(step_a.as_ref(), step_b.as_ref())
{
let min_step = min(step_a_val.abs(), step_b_val.abs()) as u64;
let diff = (base_a_val - base_b_val).abs() as u64;
let min_size = min(size_a, size_b);
if diff >= min_size && *step_a_val == *step_b_val {
return true;
}
if diff > 0 && min_step > min_size {
return true;
}
}
}
}
}
if let X86SCEVExpr::Constant(const_val) = a {
if let X86SCEVExpr::Constant(b_val) = b {
let diff = (const_val - b_val).abs() as u64;
let min_size = min(size_a, size_b);
if diff >= min_size {
return true;
}
}
}
false
}
fn scev_prove_mustalias(&self, a: &X86SCEVExpr, b: &X86SCEVExpr) -> bool {
matches!(
(a, b),
(X86SCEVExpr::Constant(va), X86SCEVExpr::Constant(vb))
if va == vb
)
}
pub fn get_scev(&self, value: u64) -> Option<&X86SCEVExpr> {
self.scev_map.get(&value)
}
pub fn get_stats(&self) -> &X86SCEVAAStats {
&self.stats
}
}
impl Default for X86SCEVAA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86GlobalsModRef {
pub global_access: HashMap<u64, ModRefInfo>,
pub function_globals: HashMap<u64, HashSet<u64>>,
pub function_behavior: HashMap<u64, FunctionModRefInfo>,
pub non_address_taken_globals: HashSet<u64>,
pub read_only_globals: HashSet<u64>,
pub stats: X86GlobalsModRefStats,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FunctionModRefInfo {
DoesNotAccessMemory,
OnlyReadsMemory,
OnlyWritesMemory,
ModRef,
OnlyAccessesArgMemory,
OnlyAccessesInaccessibleMemory,
}
impl fmt::Display for FunctionModRefInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FunctionModRefInfo::DoesNotAccessMemory => write!(f, "readnone"),
FunctionModRefInfo::OnlyReadsMemory => write!(f, "readonly"),
FunctionModRefInfo::OnlyWritesMemory => write!(f, "writeonly"),
FunctionModRefInfo::ModRef => write!(f, "modref"),
FunctionModRefInfo::OnlyAccessesArgMemory => write!(f, "argmemonly"),
FunctionModRefInfo::OnlyAccessesInaccessibleMemory => {
write!(f, "inaccessiblememonly")
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct X86GlobalsModRefStats {
pub total_globals: usize,
pub total_functions: usize,
pub non_address_taken_count: usize,
pub read_only_count: usize,
pub queries_resolved: u64,
pub noalias_results: u64,
}
impl X86GlobalsModRef {
pub fn new() -> Self {
X86GlobalsModRef {
global_access: HashMap::new(),
function_globals: HashMap::new(),
function_behavior: HashMap::new(),
non_address_taken_globals: HashSet::new(),
read_only_globals: HashSet::new(),
stats: X86GlobalsModRefStats::default(),
}
}
pub fn register_global(&mut self, global_id: u64, address_taken: bool, read_only: bool) {
self.global_access
.entry(global_id)
.or_insert(ModRefInfo::NoModRef);
if !address_taken {
self.non_address_taken_globals.insert(global_id);
}
if read_only {
self.read_only_globals.insert(global_id);
}
self.stats.total_globals = self.global_access.len();
}
pub fn register_function(&mut self, func_id: u64, behavior: FunctionModRefInfo) {
self.function_behavior.insert(func_id, behavior);
self.stats.total_functions = self.function_behavior.len();
}
pub fn record_global_access(&mut self, func_id: u64, global_id: u64, access: ModRefInfo) {
self.function_globals
.entry(func_id)
.or_default()
.insert(global_id);
let entry = self
.global_access
.entry(global_id)
.or_insert(ModRefInfo::NoModRef);
*entry = ModRefInfo::union(*entry, access);
}
pub fn get_global_access(&self, global_id: u64) -> ModRefInfo {
self.global_access
.get(&global_id)
.copied()
.unwrap_or(ModRefInfo::ModRef)
}
pub fn function_modifies_global(&self, func_id: u64, global_id: u64) -> bool {
if let Some(behavior) = self.function_behavior.get(&func_id) {
match behavior {
FunctionModRefInfo::DoesNotAccessMemory
| FunctionModRefInfo::OnlyAccessesInaccessibleMemory => {
return false;
}
FunctionModRefInfo::OnlyReadsMemory => return false,
_ => {}
}
}
if let Some(globals) = self.function_globals.get(&func_id) {
if globals.contains(&global_id) {
if let Some(access) = self.global_access.get(&global_id) {
return access.does_mod();
}
}
}
!self.non_address_taken_globals.contains(&global_id)
}
pub fn is_read_only_global(&self, global_id: u64) -> bool {
self.read_only_globals.contains(&global_id)
}
pub fn is_non_address_taken(&self, global_id: u64) -> bool {
self.non_address_taken_globals.contains(&global_id)
}
pub fn get_mod_ref_for_call(&mut self, func_id: u64, loc: &MemoryLocation) -> ModRefInfo {
self.stats.queries_resolved += 1;
if loc.ptr != 0 {
if let Some(behavior) = self.function_behavior.get(&func_id) {
match behavior {
FunctionModRefInfo::DoesNotAccessMemory => {
self.stats.noalias_results += 1;
return ModRefInfo::NoModRef;
}
FunctionModRefInfo::OnlyReadsMemory => return ModRefInfo::Ref,
FunctionModRefInfo::OnlyAccessesInaccessibleMemory => {
return ModRefInfo::NoModRef;
}
_ => {}
}
}
if self.non_address_taken_globals.contains(&loc.ptr) {
if let Some(globals) = self.function_globals.get(&func_id) {
if !globals.contains(&loc.ptr) {
self.stats.noalias_results += 1;
return ModRefInfo::NoModRef;
}
}
if let Some(access) = self.global_access.get(&loc.ptr) {
return *access;
}
}
if self.read_only_globals.contains(&loc.ptr) {
return ModRefInfo::Ref;
}
}
ModRefInfo::ModRef
}
pub fn get_stats(&self) -> &X86GlobalsModRefStats {
&self.stats
}
pub fn update_counts(&mut self) {
self.stats.non_address_taken_count = self.non_address_taken_globals.len();
self.stats.read_only_count = self.read_only_globals.len();
}
}
impl Default for X86GlobalsModRef {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct TBAANode {
pub id: u64,
pub parent_id: u64,
pub type_name: String,
pub is_constant: bool,
pub depth: usize,
pub children: Vec<u64>,
}
impl TBAANode {
pub fn new(id: u64, parent_id: u64, type_name: &str) -> Self {
TBAANode {
id,
parent_id,
type_name: type_name.to_string(),
is_constant: false,
depth: 0,
children: Vec::new(),
}
}
pub fn new_constant(id: u64, parent_id: u64, type_name: &str) -> Self {
TBAANode {
id,
parent_id,
type_name: type_name.to_string(),
is_constant: true,
depth: 0,
children: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TBAATag {
pub access_type_id: u64,
pub base_type_id: u64,
pub offset: u64,
pub is_constant: bool,
}
impl TBAATag {
pub fn new(access_type_id: u64, base_type_id: u64, offset: u64) -> Self {
TBAATag {
access_type_id,
base_type_id,
offset,
is_constant: false,
}
}
pub fn new_constant(access_type_id: u64, base_type_id: u64, offset: u64) -> Self {
TBAATag {
access_type_id,
base_type_id,
offset,
is_constant: true,
}
}
}
#[derive(Debug, Clone)]
pub struct X86TypeBasedAA {
pub type_nodes: HashMap<u64, TBAANode>,
ancestor_cache: HashMap<u64, HashSet<u64>>,
pub stats: X86TypeBasedAAStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86TypeBasedAAStats {
pub total_nodes: usize,
pub total_queries: u64,
pub noalias_results: u64,
pub mayalias_results: u64,
}
impl X86TypeBasedAA {
pub fn new() -> Self {
X86TypeBasedAA {
type_nodes: HashMap::new(),
ancestor_cache: HashMap::new(),
stats: X86TypeBasedAAStats::default(),
}
}
pub fn add_type_node(&mut self, node: TBAANode) {
if node.parent_id != 0 {
if let Some(parent) = self.type_nodes.get_mut(&node.parent_id) {
parent.children.push(node.id);
}
}
self.type_nodes.insert(node.id, node);
self.stats.total_nodes = self.type_nodes.len();
self.ancestor_cache.clear();
}
pub fn add_struct_path(&mut self, base_type_id: u64, path: &[(u64, u64)]) -> Vec<TBAATag> {
path.iter()
.map(|(type_id, offset)| TBAATag::new(*type_id, base_type_id, *offset))
.collect()
}
pub fn may_alias(&self, tag_a: Option<&TBAATag>, tag_b: Option<&TBAATag>) -> AliasResult {
let tag_a = match tag_a {
Some(t) => t,
None => return AliasResult::MayAlias,
};
let tag_b = match tag_b {
Some(t) => t,
None => return AliasResult::MayAlias,
};
if tag_a.access_type_id == tag_b.access_type_id && tag_a.offset == tag_b.offset {
return AliasResult::MustAlias;
}
let ancestors_a = self.get_ancestors(tag_a.access_type_id);
let ancestors_b = self.get_ancestors(tag_b.access_type_id);
let common: HashSet<_> = ancestors_a.intersection(&ancestors_b).copied().collect();
if common.is_empty() {
return AliasResult::NoAlias;
}
let is_ancestor_a = ancestors_a.contains(&tag_b.access_type_id);
let is_ancestor_b = ancestors_b.contains(&tag_a.access_type_id);
if is_ancestor_a || is_ancestor_b {
AliasResult::MayAlias
} else if tag_a.base_type_id == tag_b.base_type_id {
AliasResult::MayAlias
} else {
AliasResult::MayAlias
}
}
fn get_ancestors(&self, node_id: u64) -> HashSet<u64> {
if let Some(cached) = self.ancestor_cache.get(&node_id) {
return cached.clone();
}
let mut ancestors = HashSet::new();
ancestors.insert(node_id);
let mut current = node_id;
while let Some(node) = self.type_nodes.get(¤t) {
if current != node_id {
ancestors.insert(current);
}
if node.parent_id == 0 || node.parent_id == current {
break;
}
current = node.parent_id;
}
ancestors
}
pub fn is_constant_tag(&self, tag: Option<&TBAATag>) -> bool {
if let Some(tag) = tag {
if tag.is_constant {
return true;
}
if let Some(node) = self.type_nodes.get(&tag.access_type_id) {
return node.is_constant;
}
}
false
}
pub fn alias(&mut self, tag_a: Option<&TBAATag>, tag_b: Option<&TBAATag>) -> AliasResult {
self.stats.total_queries += 1;
let result = self.may_alias(tag_a, tag_b);
match result {
AliasResult::NoAlias => self.stats.noalias_results += 1,
AliasResult::MayAlias => self.stats.mayalias_results += 1,
_ => {}
}
result
}
pub fn get_stats(&self) -> &X86TypeBasedAAStats {
&self.stats
}
}
impl Default for X86TypeBasedAA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AliasScopeDomain {
pub domain_id: u64,
pub scope_id: u64,
}
impl AliasScopeDomain {
pub fn new(domain_id: u64, scope_id: u64) -> Self {
AliasScopeDomain {
domain_id,
scope_id,
}
}
}
#[derive(Debug, Clone)]
pub struct X86ScopedNoAliasAA {
pub noalias_pairs: HashSet<(u64, u64, u64)>,
pub stats: X86ScopedNoAliasAAStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86ScopedNoAliasAAStats {
pub total_queries: u64,
pub noalias_results: u64,
pub scoped_disambiguations: u64,
}
impl X86ScopedNoAliasAA {
pub fn new() -> Self {
X86ScopedNoAliasAA {
noalias_pairs: HashSet::new(),
stats: X86ScopedNoAliasAAStats::default(),
}
}
pub fn register_noalias(&mut self, domain: u64, scope_a: u64, scope_b: u64) {
self.noalias_pairs.insert((domain, scope_a, scope_b));
self.noalias_pairs.insert((domain, scope_b, scope_a));
}
pub fn may_alias(&self, loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> AliasResult {
if !loc_a.has_scoped_noalias() || !loc_b.has_scoped_noalias() {
return AliasResult::MayAlias;
}
let domain_a = match &loc_a.noalias_domain {
Some(d) => d.parse::<u64>().unwrap_or(0),
None => return AliasResult::MayAlias,
};
let domain_b = match &loc_b.noalias_domain {
Some(d) => d.parse::<u64>().unwrap_or(0),
None => return AliasResult::MayAlias,
};
if domain_a != domain_b || domain_a == 0 {
return AliasResult::MayAlias;
}
for scope_a in &loc_a.noalias_scopes {
let sid_a = scope_a.parse::<u64>().unwrap_or(0);
for scope_b in &loc_b.noalias_scopes {
let sid_b = scope_b.parse::<u64>().unwrap_or(0);
if sid_a == sid_b {
return AliasResult::MayAlias;
}
if self.noalias_pairs.contains(&(domain_a, sid_a, sid_b)) {
return AliasResult::NoAlias;
}
}
}
AliasResult::MayAlias
}
pub fn alias(&mut self, loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> AliasResult {
self.stats.total_queries += 1;
let result = self.may_alias(loc_a, loc_b);
if result == AliasResult::NoAlias {
self.stats.noalias_results += 1;
self.stats.scoped_disambiguations += 1;
}
result
}
pub fn get_stats(&self) -> &X86ScopedNoAliasAAStats {
&self.stats
}
}
impl Default for X86ScopedNoAliasAA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum X86MemoryAccessKind {
Def,
Use,
Phi,
}
#[derive(Debug, Clone)]
pub struct X86MemoryDef {
pub id: u64,
pub block_id: u64,
pub store_inst: u64,
pub defining_access: Option<u64>,
pub location: MemoryLocation,
pub is_live_on_entry: bool,
pub users: HashSet<u64>,
pub is_optimizable: bool,
}
impl X86MemoryDef {
pub fn new(id: u64, block_id: u64, store_inst: u64, location: MemoryLocation) -> Self {
X86MemoryDef {
id,
block_id,
store_inst,
defining_access: None,
location,
is_live_on_entry: false,
users: HashSet::new(),
is_optimizable: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86MemoryUse {
pub id: u64,
pub block_id: u64,
pub load_inst: u64,
pub defining_access: Option<u64>,
pub location: MemoryLocation,
pub is_optimized: bool,
}
impl X86MemoryUse {
pub fn new(id: u64, block_id: u64, load_inst: u64, location: MemoryLocation) -> Self {
X86MemoryUse {
id,
block_id,
load_inst,
defining_access: None,
location,
is_optimized: false,
}
}
}
#[derive(Debug, Clone)]
pub struct X86MemoryPhi {
pub id: u64,
pub block_id: u64,
pub incoming: Vec<(u64, u64)>,
}
impl X86MemoryPhi {
pub fn new(id: u64, block_id: u64) -> Self {
X86MemoryPhi {
id,
block_id,
incoming: Vec::new(),
}
}
pub fn add_incoming(&mut self, pred_block_id: u64, access_id: u64) {
self.incoming.push((pred_block_id, access_id));
}
pub fn get_incoming_for_pred(&self, pred_block_id: u64) -> Option<u64> {
self.incoming
.iter()
.find(|(p, _)| *p == pred_block_id)
.map(|(_, a)| *a)
}
}
#[derive(Debug, Clone)]
pub struct X86MemorySSA {
pub memory_defs: HashMap<u64, X86MemoryDef>,
pub memory_uses: HashMap<u64, X86MemoryUse>,
pub memory_phis: HashMap<u64, X86MemoryPhi>,
pub live_on_entry: Option<u64>,
pub block_to_phi: HashMap<u64, u64>,
pub inst_to_access: HashMap<u64, u64>,
next_id: u64,
pub is_optimized: bool,
pub idom: HashMap<u64, u64>,
pub predecessors: HashMap<u64, Vec<u64>>,
pub entry_block: u64,
pub stats: X86MemorySSAStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86MemorySSAStats {
pub total_accesses: usize,
pub memory_defs: usize,
pub memory_uses: usize,
pub memory_phis: usize,
pub dead_stores: usize,
pub redundant_loads: usize,
pub optimized_uses: usize,
}
impl X86MemorySSA {
pub fn new() -> Self {
X86MemorySSA {
memory_defs: HashMap::new(),
memory_uses: HashMap::new(),
memory_phis: HashMap::new(),
live_on_entry: None,
block_to_phi: HashMap::new(),
inst_to_access: HashMap::new(),
next_id: 1,
is_optimized: false,
idom: HashMap::new(),
predecessors: HashMap::new(),
entry_block: 0,
stats: X86MemorySSAStats::default(),
}
}
fn next_id(&mut self) -> u64 {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn set_dominators(&mut self, idom: HashMap<u64, u64>) {
self.idom = idom;
}
pub fn set_predecessors(&mut self, preds: HashMap<u64, Vec<u64>>) {
self.predecessors = preds;
}
pub fn set_entry_block(&mut self, block_id: u64) {
self.entry_block = block_id;
}
pub fn build(&mut self, ops: &[(u64, u64, X86Opcode, u64, u64, bool)], block_order: &[u64]) {
let live_on_entry_id = self.next_id();
self.live_on_entry = Some(live_on_entry_id);
let live_on_entry_def = X86MemoryDef {
id: live_on_entry_id,
block_id: self.entry_block,
store_inst: 0,
defining_access: None,
location: MemoryLocation::new(0, 0),
is_live_on_entry: true,
users: HashSet::new(),
is_optimizable: false,
};
self.memory_defs.insert(live_on_entry_id, live_on_entry_def);
let mut current_def_per_loc: HashMap<u64, u64> = HashMap::new();
let mut block_accesses: HashMap<u64, Vec<u64>> = HashMap::new();
for &(block_id, inst_id, _opcode, ptr, size, is_load) in ops {
let loc = MemoryLocation::new(ptr, size);
if is_load {
let use_id = self.next_id();
let defining = current_def_per_loc
.get(&ptr)
.copied()
.unwrap_or(live_on_entry_id);
let mut mem_use = X86MemoryUse::new(use_id, block_id, inst_id, loc.clone());
mem_use.defining_access = Some(defining);
if let Some(def) = self.memory_defs.get_mut(&defining) {
def.users.insert(use_id);
}
if let Some(def) = self.memory_uses.get_mut(&defining) {
}
self.memory_uses.insert(use_id, mem_use);
self.inst_to_access.insert(inst_id, use_id);
block_accesses.entry(block_id).or_default().push(use_id);
} else {
let def_id = self.next_id();
let defining = current_def_per_loc
.get(&ptr)
.copied()
.unwrap_or(live_on_entry_id);
let mut mem_def = X86MemoryDef::new(def_id, block_id, inst_id, loc.clone());
mem_def.defining_access = Some(defining);
if let Some(prev_def) = self.memory_defs.get_mut(&defining) {
prev_def.users.insert(def_id);
}
current_def_per_loc.insert(ptr, def_id);
self.memory_defs.insert(def_id, mem_def);
self.inst_to_access.insert(inst_id, def_id);
block_accesses.entry(block_id).or_default().push(def_id);
}
}
self.insert_memory_phis(block_order, &block_accesses);
self.update_stats();
}
fn insert_memory_phis(&mut self, block_order: &[u64], block_accesses: &HashMap<u64, Vec<u64>>) {
for &block_id in block_order {
if let Some(preds) = self.predecessors.get(&block_id) {
if preds.len() > 1 {
let mut incoming = Vec::new();
for &pred_id in preds {
let last_access = block_accesses
.get(&pred_id)
.and_then(|accesses| accesses.last())
.copied()
.unwrap_or_else(|| self.live_on_entry.unwrap_or(1));
incoming.push((pred_id, last_access));
}
let phi_id = self.next_id();
let mut phi = X86MemoryPhi::new(phi_id, block_id);
phi.incoming = incoming;
self.memory_phis.insert(phi_id, phi);
self.block_to_phi.insert(block_id, phi_id);
}
}
}
}
pub fn get_defining_access(&self, inst_id: u64) -> Option<u64> {
self.inst_to_access.get(&inst_id).and_then(|access_id| {
self.memory_uses
.get(access_id)
.and_then(|mem_use| mem_use.defining_access)
})
}
pub fn get_memory_access(&self, inst_id: u64) -> Option<u64> {
self.inst_to_access.get(&inst_id).copied()
}
pub fn is_same_memory_location(&self, access_a: u64, access_b: u64) -> bool {
let loc_a = self.get_location(access_a);
let loc_b = self.get_location(access_b);
match (loc_a, loc_b) {
(Some(a), Some(b)) => a == b,
_ => false,
}
}
fn get_location(&self, access_id: u64) -> Option<MemoryLocation> {
if let Some(def) = self.memory_defs.get(&access_id) {
Some(def.location.clone())
} else if let Some(use_access) = self.memory_uses.get(&access_id) {
Some(use_access.location.clone())
} else {
None
}
}
pub fn dominates_access(&self, access_a: u64, access_b: u64) -> bool {
let block_a = self.get_access_block(access_a);
let block_b = self.get_access_block(access_b);
match (block_a, block_b) {
(Some(ba), Some(bb)) => self.dominates(ba, bb),
_ => false,
}
}
fn get_access_block(&self, access_id: u64) -> Option<u64> {
if let Some(def) = self.memory_defs.get(&access_id) {
Some(def.block_id)
} else if let Some(use_access) = self.memory_uses.get(&access_id) {
Some(use_access.block_id)
} else if let Some(phi) = self.memory_phis.get(&access_id) {
Some(phi.block_id)
} else {
None
}
}
fn dominates(&self, block_a: u64, block_b: u64) -> bool {
if block_a == block_b {
return true;
}
let mut current = block_b;
let mut visited = HashSet::new();
for _ in 0..X86_MEMSSA_MAX_WALK_DEPTH {
if visited.contains(¤t) {
break;
}
visited.insert(current);
if current == block_a {
return true;
}
match self.idom.get(¤t) {
Some(dom) => {
if *dom == current {
break;
}
current = *dom;
}
None => break,
}
}
false
}
pub fn optimize_uses(&mut self) {
if self.is_optimized {
return;
}
let mut optimized_count = 0usize;
let use_ids: Vec<u64> = self.memory_uses.keys().copied().collect();
for use_id in use_ids {
if let Some(mem_use) = self.memory_uses.get(&use_id) {
let loc = mem_use.location.clone();
let mut current_def = mem_use.defining_access;
let mut walk_count = 0;
while let Some(def_id) = current_def {
walk_count += 1;
if walk_count > X86_MEMSSA_MAX_WALK_DEPTH {
break;
}
if let Some(def) = self.memory_defs.get(&def_id) {
if def.is_live_on_entry {
break;
}
if self.may_alias_locations(&def.location, &loc) {
break;
}
current_def = def.defining_access;
} else {
break;
}
}
if current_def != mem_use.defining_access && current_def.is_some() {
if let Some(mem_use) = self.memory_uses.get_mut(&use_id) {
mem_use.defining_access = current_def;
mem_use.is_optimized = true;
optimized_count += 1;
}
}
}
}
self.stats.optimized_uses = optimized_count;
self.is_optimized = true;
}
fn may_alias_locations(&self, loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> bool {
if loc_a.ptr == 0 || loc_b.ptr == 0 {
return false;
}
if loc_a.ptr == loc_b.ptr {
if loc_a.has_known_size && loc_b.has_known_size {
return true;
}
return true;
}
true
}
pub fn get_clobbering_access(
&self,
start_access: u64,
location: &MemoryLocation,
) -> Option<u64> {
let mut current = Some(start_access);
let mut depth = 0;
while let Some(access_id) = current {
depth += 1;
if depth > X86_MEMSSA_MAX_WALK_DEPTH {
break;
}
if let Some(def) = self.memory_defs.get(&access_id) {
if def.is_live_on_entry {
return Some(access_id);
}
if self.may_alias_locations(&def.location, location) {
return Some(access_id);
}
current = def.defining_access;
} else if let Some(mem_use) = self.memory_uses.get(&access_id) {
current = mem_use.defining_access;
} else if let Some(phi) = self.memory_phis.get(&access_id) {
return Some(access_id);
} else {
break;
}
}
None
}
pub fn walk_def_chain(&self, start_access: u64) -> Vec<u64> {
let mut chain = Vec::new();
let mut current = Some(start_access);
let mut depth = 0;
while let Some(access_id) = current {
depth += 1;
if depth > X86_MEMSSA_MAX_WALK_DEPTH {
break;
}
chain.push(access_id);
if let Some(def) = self.memory_defs.get(&access_id) {
if def.is_live_on_entry {
break;
}
current = def.defining_access;
} else if let Some(mem_use) = self.memory_uses.get(&access_id) {
current = mem_use.defining_access;
} else {
break;
}
}
chain
}
pub fn verify(&self) -> Result<(), String> {
for (use_id, mem_use) in &self.memory_uses {
if mem_use.defining_access.is_none() && !self.live_on_entry.is_some() {
return Err(format!("MemoryUse {} has no defining access", use_id));
}
}
for (phi_id, phi) in &self.memory_phis {
for (pred_id, incoming_id) in &phi.incoming {
if !self.memory_defs.contains_key(incoming_id)
&& !self.memory_uses.contains_key(incoming_id)
&& !self.memory_phis.contains_key(incoming_id)
{
return Err(format!(
"MemoryPhi {} has invalid incoming access {} from block {}",
phi_id, incoming_id, pred_id
));
}
}
}
for (def_id, def) in &self.memory_defs {
if !def.is_live_on_entry {
if let Some(defining) = def.defining_access {
if !self.memory_defs.contains_key(&defining)
&& !self.memory_uses.contains_key(&defining)
&& !self.memory_phis.contains_key(&defining)
{
return Err(format!(
"MemoryDef {} has invalid defining access {}",
def_id, defining
));
}
}
}
}
Ok(())
}
fn update_stats(&mut self) {
self.stats.total_accesses =
self.memory_defs.len() + self.memory_uses.len() + self.memory_phis.len();
self.stats.memory_defs = self.memory_defs.len();
self.stats.memory_uses = self.memory_uses.len();
self.stats.memory_phis = self.memory_phis.len();
}
pub fn identify_dead_stores(&self) -> Vec<u64> {
self.memory_defs
.iter()
.filter(|(_, def)| !def.is_live_on_entry && def.users.is_empty())
.map(|(id, _)| *id)
.collect()
}
pub fn identify_redundant_loads(&self) -> Vec<u64> {
let mut redundant = Vec::new();
let use_ids: Vec<_> = self.memory_uses.keys().copied().collect();
for i in 0..use_ids.len() {
let use_a_id = use_ids[i];
if let Some(use_a) = self.memory_uses.get(&use_a_id) {
for j in (i + 1)..use_ids.len() {
let use_b = &self.memory_uses[&use_ids[j]];
if use_b.location == use_a.location
&& use_b.defining_access == use_a.defining_access
&& self.dominates_access(use_a_id, use_ids[j])
{
redundant.push(use_ids[j]);
}
}
}
}
redundant
}
pub fn get_stats(&self) -> &X86MemorySSAStats {
&self.stats
}
pub fn total_accesses(&self) -> usize {
self.memory_defs.len() + self.memory_uses.len() + self.memory_phis.len()
}
}
impl Default for X86MemorySSA {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86MemorySSAUpdater {
pub mssa: X86MemorySSA,
pub update_log: Vec<String>,
}
impl X86MemorySSAUpdater {
pub fn new(mssa: X86MemorySSA) -> Self {
X86MemorySSAUpdater {
mssa,
update_log: Vec::new(),
}
}
pub fn insert_use(&mut self, inst_id: u64, block_id: u64, ptr: u64, size: u64) -> u64 {
let loc = MemoryLocation::new(ptr, size);
let defining = self.find_defining_access_in_block(block_id, &loc);
let use_id = self.mssa.next_id();
let mut mem_use = X86MemoryUse::new(use_id, block_id, inst_id, loc);
mem_use.defining_access = defining;
self.mssa.memory_uses.insert(use_id, mem_use);
self.mssa.inst_to_access.insert(inst_id, use_id);
self.mssa.is_optimized = false;
self.update_log.push(format!(
"Inserted MemoryUse {} for inst {}",
use_id, inst_id
));
use_id
}
pub fn insert_def(&mut self, inst_id: u64, block_id: u64, ptr: u64, size: u64) -> u64 {
let loc = MemoryLocation::new(ptr, size);
let defining = self.find_defining_access_in_block(block_id, &loc);
let def_id = self.mssa.next_id();
let mut mem_def = X86MemoryDef::new(def_id, block_id, inst_id, loc);
mem_def.defining_access = defining;
self.mssa.memory_defs.insert(def_id, mem_def);
self.mssa.inst_to_access.insert(inst_id, def_id);
self.mssa.is_optimized = false;
self.update_log.push(format!(
"Inserted MemoryDef {} for inst {}",
def_id, inst_id
));
def_id
}
pub fn remove_access(&mut self, access_id: u64) -> bool {
let removed = self.mssa.memory_defs.remove(&access_id).is_some()
|| self.mssa.memory_uses.remove(&access_id).is_some()
|| self.mssa.memory_phis.remove(&access_id).is_some();
if removed {
self.mssa.is_optimized = false;
self.mssa.inst_to_access.retain(|_, v| *v != access_id);
self.update_log
.push(format!("Removed access {}", access_id));
}
removed
}
pub fn move_instruction(&mut self, inst_id: u64, old_block: u64, new_block: u64) {
if let Some(access_id) = self.mssa.inst_to_access.get(&inst_id).copied() {
if let Some(def) = self.mssa.memory_defs.get_mut(&access_id) {
def.block_id = new_block;
} else if let Some(mem_use) = self.mssa.memory_uses.get_mut(&access_id) {
mem_use.block_id = new_block;
}
self.mssa.is_optimized = false;
self.update_log.push(format!(
"Moved inst {} from block {} to block {}",
inst_id, old_block, new_block
));
}
}
fn find_defining_access_in_block(
&self,
block_id: u64,
location: &MemoryLocation,
) -> Option<u64> {
let mut last_def: Option<u64> = None;
let mut block_defs: Vec<(u64, &X86MemoryDef)> = self
.mssa
.memory_defs
.iter()
.filter(|(_, d)| d.block_id == block_id && !d.is_live_on_entry)
.map(|(id, d)| (*id, d))
.collect();
block_defs.sort_by_key(|(id, _)| *id);
for (def_id, def) in &block_defs {
if location.ptr == def.location.ptr {
last_def = Some(*def_id);
}
}
if last_def.is_none() {
if let Some(preds) = self.mssa.predecessors.get(&block_id) {
for pred_id in preds {
if let Some(phi_id) = self.mssa.block_to_phi.get(pred_id) {
return Some(*phi_id);
}
}
}
}
last_def.or(self.mssa.live_on_entry)
}
pub fn get_log(&self) -> &[String] {
&self.update_log
}
}
#[derive(Debug, Clone)]
pub struct X86MemDepResult {
pub dep_inst: Option<u64>,
pub dep_kind: X86MemDepKind,
pub access_id: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum X86MemDepKind {
NonLocal,
Def,
Clobber,
NonFuncLocal,
Unknown,
}
impl fmt::Display for X86MemDepKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86MemDepKind::NonLocal => write!(f, "NonLocal"),
X86MemDepKind::Def => write!(f, "Def"),
X86MemDepKind::Clobber => write!(f, "Clobber"),
X86MemDepKind::NonFuncLocal => write!(f, "NonFuncLocal"),
X86MemDepKind::Unknown => write!(f, "Unknown"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct X86MemDepCacheKey {
ptr: u64,
size: u64,
from_inst: u64,
}
#[derive(Debug, Clone)]
pub struct X86MemDep {
pub memory_ssa: X86MemorySSA,
query_cache: HashMap<X86MemDepCacheKey, X86MemDepResult>,
batch_queries: Vec<(u64, u64, u64, u64)>,
max_cache_size: usize,
pub stats: X86MemDepStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86MemDepStats {
pub total_queries: u64,
pub cache_hits: u64,
pub cache_misses: u64,
pub batch_queries: u64,
pub nonlocal_results: u64,
pub def_results: u64,
pub clobber_results: u64,
}
impl X86MemDep {
pub fn new(memory_ssa: X86MemorySSA) -> Self {
X86MemDep {
memory_ssa,
query_cache: HashMap::new(),
batch_queries: Vec::new(),
max_cache_size: X86_MEMDEP_CACHE_SIZE,
stats: X86MemDepStats::default(),
}
}
pub fn get_dependency(&mut self, ptr: u64, size: u64, from_inst: u64) -> X86MemDepResult {
self.stats.total_queries += 1;
let cache_key = X86MemDepCacheKey {
ptr,
size,
from_inst,
};
if let Some(cached) = self.query_cache.get(&cache_key) {
self.stats.cache_hits += 1;
return cached.clone();
}
self.stats.cache_misses += 1;
let result = self.compute_dependency(ptr, size, from_inst);
if self.query_cache.len() < self.max_cache_size {
self.query_cache.insert(cache_key, result.clone());
}
result
}
fn compute_dependency(&self, ptr: u64, size: u64, from_inst: u64) -> X86MemDepResult {
let loc = MemoryLocation::new(ptr, size);
let access_id = self.memory_ssa.get_memory_access(from_inst);
match access_id {
Some(acc_id) => {
let clobber = self.memory_ssa.get_clobbering_access(acc_id, &loc);
match clobber {
Some(clobber_id) => {
if let Some(def) = self.memory_ssa.memory_defs.get(&clobber_id) {
if def.is_live_on_entry {
X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::NonLocal,
access_id: Some(clobber_id),
}
} else {
X86MemDepResult {
dep_inst: Some(def.store_inst),
dep_kind: X86MemDepKind::Def,
access_id: Some(clobber_id),
}
}
} else if self.memory_ssa.memory_phis.contains_key(&clobber_id) {
X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::Clobber,
access_id: Some(clobber_id),
}
} else {
X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::Unknown,
access_id: Some(clobber_id),
}
}
}
None => X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::NonLocal,
access_id: None,
},
}
}
None => {
X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::Unknown,
access_id: None,
}
}
}
}
pub fn get_pointer_dependency(&mut self, ptr: u64, from_inst: u64) -> X86MemDepResult {
self.get_dependency(ptr, 8, from_inst)
}
pub fn batch_add_query(&mut self, ptr: u64, size: u64, from_inst: u64) {
self.batch_queries
.push((ptr, size, from_inst, self.stats.total_queries));
if self.batch_queries.len() >= X86_BATCH_QUERY_THRESHOLD {
}
}
pub fn flush_batch(&mut self) -> Vec<(u64, X86MemDepResult)> {
let mut results = Vec::with_capacity(self.batch_queries.len());
for &(ptr, size, from_inst, _seq) in &self.batch_queries {
self.stats.total_queries += 1;
self.stats.batch_queries += 1;
let cache_key = X86MemDepCacheKey {
ptr,
size,
from_inst,
};
if let Some(cached) = self.query_cache.get(&cache_key) {
self.stats.cache_hits += 1;
results.push((from_inst, cached.clone()));
} else {
self.stats.cache_misses += 1;
let result = self.compute_dependency(ptr, size, from_inst);
if self.query_cache.len() < self.max_cache_size {
self.query_cache.insert(cache_key, result.clone());
}
results.push((from_inst, result));
}
}
self.batch_queries.clear();
results
}
pub fn invalidate_cache(&mut self) {
self.query_cache.clear();
}
pub fn invalidate_inst(&mut self, inst_id: u64) {
self.query_cache.retain(|key, _| key.from_inst != inst_id);
}
pub fn get_stats(&self) -> &X86MemDepStats {
&self.stats
}
pub fn pending_batch_queries(&self) -> usize {
self.batch_queries.len()
}
pub fn cache_size(&self) -> usize {
self.query_cache.len()
}
}
#[derive(Debug, Clone)]
pub struct X86AliasAnalysis {
pub basic_aa: X86BasicAA,
pub cfl_aa: X86CFLAA,
pub scev_aa: X86SCEVAA,
pub globals_mod_ref: X86GlobalsModRef,
pub tbaa: X86TypeBasedAA,
pub scoped_noalias_aa: X86ScopedNoAliasAA,
pub memory_ssa: X86MemorySSA,
pub mem_dep: Option<X86MemDep>,
pub alias_sets: HashMap<u64, AliasSet>,
pub points_to: HashMap<u64, PointsToSet>,
pub total_queries: u64,
alias_cache: HashMap<(u64, u64, u64, u64), AliasResult>,
max_alias_cache_size: usize,
pub initialized: bool,
}
impl X86AliasAnalysis {
pub fn new() -> Self {
X86AliasAnalysis {
basic_aa: X86BasicAA::new(),
cfl_aa: X86CFLAA::new(),
scev_aa: X86SCEVAA::new(),
globals_mod_ref: X86GlobalsModRef::new(),
tbaa: X86TypeBasedAA::new(),
scoped_noalias_aa: X86ScopedNoAliasAA::new(),
memory_ssa: X86MemorySSA::new(),
mem_dep: None,
alias_sets: HashMap::new(),
points_to: HashMap::new(),
total_queries: 0,
alias_cache: HashMap::new(),
max_alias_cache_size: 2048,
initialized: false,
}
}
pub fn initialize(&mut self) {
self.cfl_aa.solve();
self.memory_ssa.optimize_uses();
self.mem_dep = Some(X86MemDep::new(self.memory_ssa.clone()));
self.initialized = true;
}
pub fn alias(&mut self, loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> X86AliasResult {
self.total_queries += 1;
let cache_key = (loc_a.ptr, loc_b.ptr, loc_a.size, loc_b.size);
if let Some(&cached) = self.alias_cache.get(&cache_key) {
return X86AliasResult::new(cached, "cache");
}
let basic_result = self.basic_aa.alias(loc_a, loc_b);
if basic_result == AliasResult::NoAlias || basic_result == AliasResult::MustAlias {
self.cache_result(cache_key, basic_result);
return X86AliasResult::new(basic_result, "BasicAA");
}
if loc_a.has_scoped_noalias() || loc_b.has_scoped_noalias() {
let scoped_result = self.scoped_noalias_aa.alias(loc_a, loc_b);
if scoped_result == AliasResult::NoAlias {
self.cache_result(cache_key, scoped_result);
return X86AliasResult::new(scoped_result, "ScopedNoAliasAA");
}
}
if loc_a.has_tbaa() || loc_b.has_tbaa() {
let tbaa_result = self.tbaa.alias(None, None); if tbaa_result == AliasResult::NoAlias {
self.cache_result(cache_key, tbaa_result);
return X86AliasResult::new(tbaa_result, "TypeBasedAA");
}
}
let scev_result = self.scev_aa.alias(loc_a, loc_b);
if scev_result == AliasResult::NoAlias {
self.cache_result(cache_key, scev_result);
return X86AliasResult::new(scev_result, "SCEVAA");
}
let cfl_result = self.cfl_aa.may_alias(loc_a.ptr, loc_b.ptr);
if cfl_result == AliasResult::NoAlias {
self.cache_result(cache_key, cfl_result);
return X86AliasResult::new(cfl_result, "CFLAA");
}
let final_result = AliasResult::MayAlias;
self.cache_result(cache_key, final_result);
X86AliasResult::new(final_result, "default")
}
fn cache_result(&mut self, key: (u64, u64, u64, u64), result: AliasResult) {
if self.alias_cache.len() >= self.max_alias_cache_size {
let half = self.max_alias_cache_size / 2;
let keys: Vec<_> = self.alias_cache.keys().take(half).cloned().collect();
for k in keys {
self.alias_cache.remove(&k);
}
}
self.alias_cache.insert(key, result);
}
pub fn get_mod_ref_for_call(&mut self, func_id: u64, loc: &MemoryLocation) -> ModRefInfo {
let gmr = self.globals_mod_ref.get_mod_ref_for_call(func_id, loc);
if gmr != ModRefInfo::ModRef {
return gmr;
}
if self.basic_aa.points_to_constant_memory(loc) {
return ModRefInfo::Ref;
}
ModRefInfo::ModRef
}
pub fn points_to_constant_memory(&self, loc: &MemoryLocation) -> bool {
self.basic_aa.points_to_constant_memory(loc)
}
pub fn get_points_to(&self, ptr: u64) -> PointsToSet {
self.cfl_aa.get_points_to(ptr)
}
pub fn get_alias_set(&mut self, ptr: u64) -> &mut AliasSet {
self.alias_sets
.entry(ptr)
.or_insert_with(|| AliasSet::new(ptr))
}
pub fn merge_alias_sets(&mut self, ptr_a: u64, ptr_b: u64) {
self.alias_sets
.entry(ptr_a)
.or_insert_with(|| AliasSet::new(ptr_a));
self.alias_sets
.entry(ptr_b)
.or_insert_with(|| AliasSet::new(ptr_b));
let set_b = self.alias_sets[&ptr_b].clone();
if let Some(set_a) = self.alias_sets.get_mut(&ptr_a) {
set_a.merge(&set_b);
}
}
pub fn invalidate_all(&mut self) {
self.alias_cache.clear();
if let Some(ref mut mem_dep) = self.mem_dep {
mem_dep.invalidate_cache();
}
}
pub fn build_memory_ssa(
&mut self,
ops: &[(u64, u64, X86Opcode, u64, u64, bool)],
block_order: &[u64],
) {
self.memory_ssa.build(ops, block_order);
}
pub fn optimize_memory_ssa(&mut self) {
self.memory_ssa.optimize_uses();
}
pub fn get_mem_dep(&mut self, ptr: u64, size: u64, from_inst: u64) -> X86MemDepResult {
if self.mem_dep.is_none() {
self.initialize();
}
self.mem_dep
.as_mut()
.map(|md| md.get_dependency(ptr, size, from_inst))
.unwrap_or(X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::Unknown,
access_id: None,
})
}
pub fn print_stats(&self) -> String {
let mut s = String::new();
s.push_str(&format!("=== X86 Alias Analysis Stats ===\n"));
s.push_str(&format!("Total queries: {}\n", self.total_queries));
s.push_str(&format!(
"BasicAA: total={}, noalias={}, mustalias={}, mayalias={}\n",
self.basic_aa.stats.total_queries,
self.basic_aa.stats.noalias_results,
self.basic_aa.stats.mustalias_results,
self.basic_aa.stats.mayalias_results
));
s.push_str(&format!(
"CFLAA: nodes={}, edges={}, iterations={}, queries={}\n",
self.cfl_aa.stats.total_nodes,
self.cfl_aa.stats.total_edges,
self.cfl_aa.stats.iterations_to_fixpoint,
self.cfl_aa.stats.queries_resolved,
));
s.push_str(&format!(
"SCEVAA: queries={}, noalias={}, disambiguations={}\n",
self.scev_aa.stats.total_queries,
self.scev_aa.stats.noalias_results,
self.scev_aa.stats.scev_disambiguations,
));
s.push_str(&format!(
"GlobalsModRef: globals={}, functions={}, noalias={}\n",
self.globals_mod_ref.stats.total_globals,
self.globals_mod_ref.stats.total_functions,
self.globals_mod_ref.stats.noalias_results,
));
s.push_str(&format!(
"TypeBasedAA: nodes={}, queries={}, noalias={}\n",
self.tbaa.stats.total_nodes,
self.tbaa.stats.total_queries,
self.tbaa.stats.noalias_results,
));
s.push_str(&format!(
"ScopedNoAliasAA: queries={}, noalias={}, disambiguations={}\n",
self.scoped_noalias_aa.stats.total_queries,
self.scoped_noalias_aa.stats.noalias_results,
self.scoped_noalias_aa.stats.scoped_disambiguations,
));
s.push_str(&format!(
"MemorySSA: accesses={}, defs={}, uses={}, phis={}, optimized={}\n",
self.memory_ssa.stats.total_accesses,
self.memory_ssa.stats.memory_defs,
self.memory_ssa.stats.memory_uses,
self.memory_ssa.stats.memory_phis,
self.memory_ssa.stats.optimized_uses,
));
s.push_str(&format!("AliasSets: {}\n", self.alias_sets.len()));
s
}
}
impl Default for X86AliasAnalysis {
fn default() -> Self {
Self::new()
}
}
pub fn build_x86_alias_analysis_pipeline() -> X86AliasAnalysis {
let mut aa = X86AliasAnalysis::new();
aa.initialize();
aa
}
pub fn quick_alias_check(loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> AliasResult {
let mut basic_aa = X86BasicAA::new();
basic_aa.alias(loc_a, loc_b)
}
pub fn is_memory_operation(opcode: X86Opcode) -> bool {
matches!(
opcode,
X86Opcode::Load
| X86Opcode::Store
| X86Opcode::AtomicCmpXchg
| X86Opcode::AtomicRMW
| X86Opcode::Alloca
)
}
pub fn may_read_memory(opcode: X86Opcode) -> bool {
matches!(
opcode,
X86Opcode::Load
| X86Opcode::AtomicCmpXchg
| X86Opcode::AtomicRMW
| X86Opcode::Call
| X86Opcode::Invoke
)
}
pub fn may_write_memory(opcode: X86Opcode) -> bool {
matches!(
opcode,
X86Opcode::Store
| X86Opcode::AtomicCmpXchg
| X86Opcode::AtomicRMW
| X86Opcode::Call
| X86Opcode::Invoke
)
}
pub fn get_intrinsic_mod_ref(name: &str) -> Option<ModRefInfo> {
match name {
"llvm.memcpy" | "llvm.memmove" | "llvm.memset" => Some(ModRefInfo::ModRef),
"llvm.sqrt" | "llvm.sin" | "llvm.cos" | "llvm.exp" | "llvm.log" => {
Some(ModRefInfo::NoModRef)
}
"llvm.lifetime.start"
| "llvm.lifetime.end"
| "llvm.invariant.start"
| "llvm.invariant.end" => Some(ModRefInfo::NoModRef),
"llvm.assume" => Some(ModRefInfo::NoModRef),
"llvm.donothing" => Some(ModRefInfo::NoModRef),
"llvm.experimental.noalias.scope.decl" => Some(ModRefInfo::NoModRef),
_ => None,
}
}
pub fn have_same_base_object(aa: &X86AliasAnalysis, ptr_a: u64, ptr_b: u64) -> bool {
if ptr_a == ptr_b && ptr_a != 0 {
if aa.basic_aa.alloca_sizes.contains_key(&ptr_a) {
return true;
}
if aa.basic_aa.global_sizes.contains_key(&ptr_a) {
return true;
}
}
false
}
pub fn get_underlying_object(aa: &X86AliasAnalysis, ptr: u64) -> Option<u64> {
if aa.basic_aa.alloca_sizes.contains_key(&ptr) || aa.basic_aa.global_sizes.contains_key(&ptr) {
return Some(ptr);
}
let mut current = ptr;
let mut visited = HashSet::new();
for _ in 0..X86_ALIAS_MAX_RECURSION_DEPTH {
if visited.contains(¤t) {
break;
}
visited.insert(current);
if aa.basic_aa.alloca_sizes.contains_key(¤t)
|| aa.basic_aa.global_sizes.contains_key(¤t)
{
return Some(current);
}
match aa.basic_aa.gep_bases.get(¤t) {
Some(base) => current = *base,
None => break,
}
}
None
}
pub fn get_pointer_offset(aa: &X86AliasAnalysis, ptr: u64) -> Option<i64> {
if aa.basic_aa.alloca_sizes.contains_key(&ptr) || aa.basic_aa.global_sizes.contains_key(&ptr) {
return Some(0);
}
aa.basic_aa.gep_offsets.get(&ptr).map(|&(_, offset)| offset)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_memloc(ptr: u64, size: u64) -> MemoryLocation {
MemoryLocation::new(ptr, size)
}
fn make_tbaa_tag(access_type_id: u64, base_type_id: u64, offset: u64) -> TBAATag {
TBAATag::new(access_type_id, base_type_id, offset)
}
fn make_memloc_with_tbaa(ptr: u64, size: u64, _tag_str: &str) -> MemoryLocation {
MemoryLocation {
ptr,
size,
tbaa_tag: Some(_tag_str.to_string()),
noalias_domain: None,
noalias_scopes: Vec::new(),
has_known_size: size > 0,
}
}
fn make_memloc_with_noalias(
ptr: u64,
size: u64,
domain: u64,
scopes: Vec<u64>,
) -> MemoryLocation {
MemoryLocation {
ptr,
size,
tbaa_tag: None,
noalias_domain: Some(domain.to_string()),
noalias_scopes: scopes.iter().map(|s| s.to_string()).collect(),
has_known_size: size > 0,
}
}
#[test]
fn test_alias_result_display() {
assert_eq!(format!("{}", AliasResult::NoAlias), "NoAlias");
assert_eq!(format!("{}", AliasResult::MayAlias), "MayAlias");
assert_eq!(format!("{}", AliasResult::PartialAlias), "PartialAlias");
assert_eq!(format!("{}", AliasResult::MustAlias), "MustAlias");
}
#[test]
fn test_alias_result_ordering() {
assert!(AliasResult::NoAlias < AliasResult::MayAlias);
assert!(AliasResult::MayAlias < AliasResult::PartialAlias);
assert!(AliasResult::PartialAlias < AliasResult::MustAlias);
}
#[test]
fn test_alias_result_meet() {
assert_eq!(
AliasResult::meet(AliasResult::NoAlias, AliasResult::MayAlias),
AliasResult::NoAlias
);
assert_eq!(
AliasResult::meet(AliasResult::MustAlias, AliasResult::MayAlias),
AliasResult::MayAlias
);
assert_eq!(
AliasResult::meet(AliasResult::MayAlias, AliasResult::PartialAlias),
AliasResult::MayAlias
);
}
#[test]
fn test_alias_result_is_noalias() {
assert!(AliasResult::NoAlias.is_no_alias());
assert!(!AliasResult::MayAlias.is_no_alias());
assert!(!AliasResult::MustAlias.is_no_alias());
}
#[test]
fn test_alias_result_is_mustalias() {
assert!(!AliasResult::NoAlias.is_must_alias());
assert!(AliasResult::MustAlias.is_must_alias());
assert!(!AliasResult::MayAlias.is_must_alias());
}
#[test]
fn test_mod_ref_info_does_ref() {
assert!(!ModRefInfo::NoModRef.does_ref());
assert!(ModRefInfo::Ref.does_ref());
assert!(!ModRefInfo::Mod.does_ref());
assert!(ModRefInfo::ModRef.does_ref());
}
#[test]
fn test_mod_ref_info_does_mod() {
assert!(!ModRefInfo::NoModRef.does_mod());
assert!(!ModRefInfo::Ref.does_mod());
assert!(ModRefInfo::Mod.does_mod());
assert!(ModRefInfo::ModRef.does_mod());
}
#[test]
fn test_mod_ref_info_union() {
assert_eq!(
ModRefInfo::union(ModRefInfo::NoModRef, ModRefInfo::Ref),
ModRefInfo::Ref
);
assert_eq!(
ModRefInfo::union(ModRefInfo::Ref, ModRefInfo::Mod),
ModRefInfo::ModRef
);
assert_eq!(
ModRefInfo::union(ModRefInfo::Mod, ModRefInfo::Mod),
ModRefInfo::Mod
);
}
#[test]
fn test_mod_ref_info_meet() {
assert_eq!(
ModRefInfo::meet(ModRefInfo::ModRef, ModRefInfo::Ref),
ModRefInfo::Ref
);
assert_eq!(
ModRefInfo::meet(ModRefInfo::Ref, ModRefInfo::Mod),
ModRefInfo::NoModRef
);
}
#[test]
fn test_mod_ref_info_clear_mod() {
let mut info = ModRefInfo::ModRef;
info.clear_mod();
assert_eq!(info, ModRefInfo::Ref);
let mut info = ModRefInfo::Mod;
info.clear_mod();
assert_eq!(info, ModRefInfo::NoModRef);
}
#[test]
fn test_mod_ref_info_clear_ref() {
let mut info = ModRefInfo::ModRef;
info.clear_ref();
assert_eq!(info, ModRefInfo::Mod);
let mut info = ModRefInfo::Ref;
info.clear_ref();
assert_eq!(info, ModRefInfo::NoModRef);
}
#[test]
fn test_mod_ref_info_display() {
assert_eq!(format!("{}", ModRefInfo::NoModRef), "NoModRef");
assert_eq!(format!("{}", ModRefInfo::Ref), "Ref");
assert_eq!(format!("{}", ModRefInfo::Mod), "Mod");
assert_eq!(format!("{}", ModRefInfo::ModRef), "ModRef");
}
#[test]
fn test_memory_location_new() {
let loc = MemoryLocation::new(100, 8);
assert_eq!(loc.ptr, 100);
assert_eq!(loc.size, 8);
assert!(loc.has_known_size);
assert!(!loc.has_tbaa());
assert!(!loc.has_scoped_noalias());
}
#[test]
fn test_memory_location_with_tbaa() {
let loc = MemoryLocation::new(100, 8).with_tbaa("int".to_string());
assert!(loc.has_tbaa());
assert_eq!(loc.tbaa_tag, Some("int".to_string()));
}
#[test]
fn test_memory_location_with_scoped_noalias() {
let loc = MemoryLocation::new(100, 8)
.with_scoped_noalias("1".to_string(), vec!["10".to_string(), "20".to_string()]);
assert!(loc.has_scoped_noalias());
assert_eq!(loc.noalias_domain, Some("1".to_string()));
assert_eq!(loc.noalias_scopes.len(), 2);
}
#[test]
fn test_memory_location_unknown_size() {
let loc = MemoryLocation::new(100, 0);
assert!(!loc.has_known_size);
}
#[test]
fn test_points_to_set_empty() {
let pts = PointsToSet::empty();
assert!(pts.is_empty());
assert!(!pts.includes_null);
assert!(!pts.includes_unknown);
}
#[test]
fn test_points_to_set_singleton() {
let pts = PointsToSet::singleton(42);
assert_eq!(pts.len(), 1);
assert!(pts.objects.contains(&42));
}
#[test]
fn test_points_to_set_unknown() {
let pts = PointsToSet::unknown();
assert!(pts.includes_unknown);
assert!(pts.includes_null);
}
#[test]
fn test_points_to_set_may_alias() {
let mut a = PointsToSet::singleton(10);
let b = PointsToSet::singleton(10);
assert!(a.may_alias(&b));
let c = PointsToSet::singleton(20);
assert!(!a.may_alias(&c));
a.includes_unknown = true;
assert!(a.may_alias(&c));
}
#[test]
fn test_points_to_set_noalias() {
let a = PointsToSet::noalias();
let b = PointsToSet::singleton(10);
assert!(!a.may_alias(&b));
}
#[test]
fn test_points_to_set_union() {
let mut a = PointsToSet::singleton(1);
let b = PointsToSet::singleton(2);
a.union_with(&b);
assert_eq!(a.len(), 2);
assert!(a.objects.contains(&1));
assert!(a.objects.contains(&2));
}
#[test]
fn test_alias_set_new() {
let set = AliasSet::new(100);
assert_eq!(set.id, 100);
assert!(set.pointers.is_empty());
assert!(!set.has_unknown);
}
#[test]
fn test_alias_set_add_pointer() {
let mut set = AliasSet::new(0);
set.add_pointer(42);
assert!(set.pointers.contains(&42));
set.add_pointer(99);
assert_eq!(set.pointers.len(), 2);
}
#[test]
fn test_alias_set_merge() {
let mut a = AliasSet::new(1);
a.add_pointer(10);
a.access_type = AliasSetAccess::Ref;
let mut b = AliasSet::new(2);
b.add_pointer(20);
b.access_type = AliasSetAccess::Mod;
a.merge(&b);
assert!(a.pointers.contains(&10));
assert!(a.pointers.contains(&20));
assert_eq!(a.access_type, AliasSetAccess::ModRef);
}
#[test]
fn test_basic_aa_new() {
let basic = X86BasicAA::new();
assert!(basic.alloca_sizes.is_empty());
assert!(basic.global_sizes.is_empty());
}
#[test]
fn test_basic_aa_same_pointer_must_alias() {
let mut basic = X86BasicAA::new();
let loc_a = make_memloc(100, 8);
let loc_b = make_memloc(100, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::MustAlias);
}
#[test]
fn test_basic_aa_different_allocas_no_alias() {
let mut basic = X86BasicAA::new();
basic.register_alloca(100, 8);
basic.register_alloca(200, 8);
let loc_a = make_memloc(100, 8);
let loc_b = make_memloc(200, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_basic_aa_different_globals_no_alias() {
let mut basic = X86BasicAA::new();
basic.register_global(300, 4, false);
basic.register_global(400, 4, false);
let loc_a = make_memloc(300, 4);
let loc_b = make_memloc(400, 4);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_basic_aa_alloca_vs_global_no_alias() {
let mut basic = X86BasicAA::new();
basic.register_alloca(100, 8);
basic.register_global(500, 8, false);
let loc_a = make_memloc(100, 8);
let loc_b = make_memloc(500, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_basic_aa_gep_same_base_different_offsets() {
let mut basic = X86BasicAA::new();
basic.register_alloca(1, 64);
basic.register_gep(10, 1, 0);
basic.register_gep(20, 1, 16);
let loc_a = make_memloc(10, 8); let loc_b = make_memloc(20, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_basic_aa_gep_same_base_overlapping() {
let mut basic = X86BasicAA::new();
basic.register_alloca(1, 64);
basic.register_gep(10, 1, 0);
basic.register_gep(20, 1, 4);
let loc_a = make_memloc(10, 8); let loc_b = make_memloc(20, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::PartialAlias);
}
#[test]
fn test_basic_aa_gep_different_bases_no_alias() {
let mut basic = X86BasicAA::new();
basic.register_alloca(1, 64);
basic.register_alloca(2, 64);
basic.register_gep(10, 1, 0);
basic.register_gep(20, 2, 0);
let loc_a = make_memloc(10, 8);
let loc_b = make_memloc(20, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_basic_aa_function_pointer_no_alias() {
let mut basic = X86BasicAA::new();
basic.function_addresses.insert(999);
let loc_a = make_memloc(999, 8);
let loc_b = make_memloc(100, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_basic_aa_points_to_constant_memory() {
let mut basic = X86BasicAA::new();
basic.register_global(500, 8, true);
let loc = make_memloc(500, 8);
assert!(basic.points_to_constant_memory(&loc));
let loc2 = make_memloc(600, 8);
assert!(!basic.points_to_constant_memory(&loc2));
}
#[test]
fn test_basic_aa_stats() {
let mut basic = X86BasicAA::new();
basic.register_alloca(100, 4);
basic.register_alloca(200, 4);
let loc_a = make_memloc(100, 4);
let loc_b = make_memloc(200, 4);
basic.alias(&loc_a, &loc_b);
let stats = basic.get_stats();
assert_eq!(stats.total_queries, 1);
assert_eq!(stats.noalias_results, 1);
}
#[test]
fn test_cfl_aa_new() {
let cfl = X86CFLAA::new();
assert!(!cfl.solved);
assert_eq!(cfl.stats.total_nodes, 0);
}
#[test]
fn test_cfl_aa_register_and_query() {
let mut cfl = X86CFLAA::new();
cfl.register_pointer(1);
cfl.register_pointer(2);
cfl.add_points_to(1, 100);
cfl.add_points_to(2, 200);
cfl.solve();
assert_eq!(cfl.may_alias(1, 1), AliasResult::MustAlias);
assert_eq!(cfl.may_alias(1, 2), AliasResult::NoAlias);
}
#[test]
fn test_cfl_aa_assign_propagation() {
let mut cfl = X86CFLAA::new();
cfl.register_pointer(1);
cfl.register_pointer(2);
cfl.add_points_to(1, 100);
cfl.add_assign(2, 1); cfl.solve();
let pts2 = cfl.get_points_to(2);
assert!(pts2.objects.contains(&100));
}
#[test]
fn test_cfl_aa_load_store() {
let mut cfl = X86CFLAA::new();
cfl.register_pointer(1); cfl.register_pointer(2); cfl.register_pointer(3);
cfl.add_points_to(1, 100); cfl.add_points_to(100, 200);
cfl.add_load(2, 1);
cfl.add_points_to(3, 300);
cfl.add_store(1, 3);
cfl.solve();
let pts2 = cfl.get_points_to(2);
assert!(pts2.objects.contains(&200));
let pts100 = cfl.get_points_to(100);
assert!(pts100.objects.contains(&300));
}
#[test]
fn test_cfl_aa_escaped() {
let mut cfl = X86CFLAA::new();
cfl.register_pointer(1);
cfl.mark_escaped(1);
cfl.solve();
let pts = cfl.get_points_to(1);
assert!(pts.includes_unknown);
}
#[test]
fn test_cfl_aa_must_alias() {
let mut cfl = X86CFLAA::new();
cfl.register_pointer(1);
cfl.register_pointer(2);
cfl.add_points_to(1, 42);
cfl.add_points_to(2, 42);
cfl.solve();
assert!(cfl.must_alias(1, 2));
}
#[test]
fn test_cfl_aa_stats_tracking() {
let mut cfl = X86CFLAA::new();
cfl.register_pointer(1);
cfl.register_pointer(2);
cfl.add_assign(1, 2);
cfl.add_load(1, 2);
cfl.add_store(1, 2);
cfl.solve();
assert_eq!(cfl.stats.assign_edges, 1);
assert_eq!(cfl.stats.load_edges, 1);
assert_eq!(cfl.stats.store_edges, 1);
}
#[test]
fn test_scev_expr_constant() {
let expr = X86SCEVExpr::constant(42);
assert!(expr.is_constant());
assert_eq!(expr.evaluate_at(10), Some(42));
}
#[test]
fn test_scev_expr_addrec() {
let expr = X86SCEVExpr::addrec(X86SCEVExpr::constant(5), X86SCEVExpr::constant(2), 1);
assert_eq!(expr.evaluate_at(0), Some(5));
assert_eq!(expr.evaluate_at(1), Some(7));
assert_eq!(expr.evaluate_at(3), Some(11));
}
#[test]
fn test_scev_expr_arithmetic() {
let a = X86SCEVExpr::constant(3);
let b = X86SCEVExpr::constant(4);
let sum = X86SCEVExpr::Add(Box::new(a), Box::new(b));
assert_eq!(sum.evaluate_at(0), Some(7));
let a = X86SCEVExpr::constant(3);
let b = X86SCEVExpr::constant(4);
let prod = X86SCEVExpr::Mul(Box::new(a), Box::new(b));
assert_eq!(prod.evaluate_at(0), Some(12));
}
#[test]
fn test_scev_expr_loop_invariant() {
let expr = X86SCEVExpr::constant(42);
assert!(expr.is_loop_invariant(1));
let addrec = X86SCEVExpr::addrec(X86SCEVExpr::constant(0), X86SCEVExpr::constant(1), 1);
assert!(!addrec.is_loop_invariant(1));
assert!(addrec.is_loop_invariant(2));
}
#[test]
fn test_scev_aa_new() {
let scev_aa = X86SCEVAA::new();
assert!(scev_aa.scev_map.is_empty());
}
#[test]
fn test_scev_aa_noalias() {
let mut scev_aa = X86SCEVAA::new();
scev_aa.register_scev(1, X86SCEVExpr::constant(0));
scev_aa.register_scev(2, X86SCEVExpr::constant(64));
let loc_a = make_memloc(1, 8);
let loc_b = make_memloc(2, 8);
assert_eq!(scev_aa.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_scev_aa_may_alias() {
let mut scev_aa = X86SCEVAA::new();
scev_aa.register_scev(1, X86SCEVExpr::constant(0));
scev_aa.register_scev(2, X86SCEVExpr::constant(4));
let loc_a = make_memloc(1, 8);
let loc_b = make_memloc(2, 8);
assert_eq!(scev_aa.alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_scev_aa_addrec_noalias() {
let mut scev_aa = X86SCEVAA::new();
let a = X86SCEVExpr::addrec(X86SCEVExpr::constant(0), X86SCEVExpr::constant(8), 1);
let b = X86SCEVExpr::addrec(X86SCEVExpr::constant(64), X86SCEVExpr::constant(8), 1);
scev_aa.register_scev(1, a);
scev_aa.register_scev(2, b);
let loc_a = make_memloc(1, 8);
let loc_b = make_memloc(2, 8);
assert_eq!(scev_aa.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_globals_mod_ref_new() {
let gmr = X86GlobalsModRef::new();
assert!(gmr.global_access.is_empty());
assert!(gmr.function_behavior.is_empty());
}
#[test]
fn test_globals_mod_ref_readnone() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_function(100, FunctionModRefInfo::DoesNotAccessMemory);
let loc = make_memloc(1, 8);
assert_eq!(gmr.get_mod_ref_for_call(100, &loc), ModRefInfo::NoModRef);
}
#[test]
fn test_globals_mod_ref_readonly() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_function(100, FunctionModRefInfo::OnlyReadsMemory);
let loc = make_memloc(1, 8);
assert_eq!(gmr.get_mod_ref_for_call(100, &loc), ModRefInfo::Ref);
}
#[test]
fn test_globals_mod_ref_non_address_taken() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_global(42, false, false); gmr.register_function(100, FunctionModRefInfo::ModRef);
gmr.record_global_access(100, 42, ModRefInfo::Ref);
let loc = make_memloc(42, 8);
assert_eq!(gmr.get_mod_ref_for_call(100, &loc), ModRefInfo::Ref);
}
#[test]
fn test_globals_mod_ref_read_only_global() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_global(42, true, true);
let loc = make_memloc(42, 8);
assert_eq!(gmr.get_mod_ref_for_call(999, &loc), ModRefInfo::Ref);
}
#[test]
fn test_globals_mod_ref_function_modifies_global() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_global(42, false, false);
gmr.register_function(100, FunctionModRefInfo::ModRef);
gmr.record_global_access(100, 42, ModRefInfo::Mod);
assert!(gmr.function_modifies_global(100, 42));
assert!(!gmr.function_modifies_global(200, 42));
}
#[test]
fn test_tbaa_new() {
let tbaa = X86TypeBasedAA::new();
assert_eq!(tbaa.stats.total_nodes, 0);
}
#[test]
fn test_tbaa_add_node() {
let mut tbaa = X86TypeBasedAA::new();
let node = TBAANode::new(1, 0, "int");
tbaa.add_type_node(node);
assert_eq!(tbaa.stats.total_nodes, 1);
assert!(tbaa.type_nodes.contains_key(&1));
}
#[test]
fn test_tbaa_may_alias_same_type() {
let mut tbaa = X86TypeBasedAA::new();
tbaa.add_type_node(TBAANode::new(1, 0, "root"));
tbaa.add_type_node(TBAANode::new(2, 1, "int"));
let tag_a = make_tbaa_tag(2, 1, 0);
let tag_b = make_tbaa_tag(2, 1, 0);
assert_eq!(
tbaa.may_alias(Some(&tag_a), Some(&tag_b)),
AliasResult::MustAlias
);
}
#[test]
fn test_tbaa_may_alias_different_roots() {
let mut tbaa = X86TypeBasedAA::new();
tbaa.add_type_node(TBAANode::new(1, 0, "root1"));
tbaa.add_type_node(TBAANode::new(2, 1, "int"));
tbaa.add_type_node(TBAANode::new(3, 0, "root2"));
tbaa.add_type_node(TBAANode::new(4, 3, "float"));
let tag_a = make_tbaa_tag(2, 1, 0);
let tag_b = make_tbaa_tag(4, 3, 0);
assert_eq!(
tbaa.may_alias(Some(&tag_a), Some(&tag_b)),
AliasResult::NoAlias
);
}
#[test]
fn test_tbaa_missing_tag_returns_may_alias() {
let tbaa = X86TypeBasedAA::new();
assert_eq!(tbaa.may_alias(None, None), AliasResult::MayAlias);
}
#[test]
fn test_tbaa_constant_tag() {
let mut tbaa = X86TypeBasedAA::new();
tbaa.add_type_node(TBAANode::new_constant(1, 0, "const_root"));
let tag = TBAATag::new_constant(1, 0, 0);
assert!(tbaa.is_constant_tag(Some(&tag)));
}
#[test]
fn test_tbaa_stats() {
let mut tbaa = X86TypeBasedAA::new();
tbaa.add_type_node(TBAANode::new(1, 0, "root"));
tbaa.add_type_node(TBAANode::new(2, 1, "child"));
let tag_a = make_tbaa_tag(1, 1, 0);
let tag_b = make_tbaa_tag(2, 1, 0);
tbaa.alias(Some(&tag_a), Some(&tag_b));
let stats = tbaa.get_stats();
assert_eq!(stats.total_queries, 1);
}
#[test]
fn test_scoped_noalias_new() {
let aa = X86ScopedNoAliasAA::new();
assert!(aa.noalias_pairs.is_empty());
}
#[test]
fn test_scoped_noalias_different_scopes() {
let mut aa = X86ScopedNoAliasAA::new();
aa.register_noalias(1, 10, 20);
let loc_a = make_memloc_with_noalias(100, 8, 1, vec![10]);
let loc_b = make_memloc_with_noalias(200, 8, 1, vec![20]);
assert_eq!(aa.may_alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_scoped_noalias_same_scope() {
let mut aa = X86ScopedNoAliasAA::new();
let loc_a = make_memloc_with_noalias(100, 8, 1, vec![10]);
let loc_b = make_memloc_with_noalias(200, 8, 1, vec![10]);
assert_eq!(aa.may_alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_scoped_noalias_different_domains() {
let mut aa = X86ScopedNoAliasAA::new();
aa.register_noalias(1, 10, 20);
let loc_a = make_memloc_with_noalias(100, 8, 1, vec![10]);
let loc_b = make_memloc_with_noalias(200, 8, 2, vec![20]);
assert_eq!(aa.may_alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_scoped_noalias_no_metadata() {
let aa = X86ScopedNoAliasAA::new();
let loc_a = make_memloc(100, 8);
let loc_b = make_memloc(200, 8);
assert_eq!(aa.may_alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_scoped_noalias_stats() {
let mut aa = X86ScopedNoAliasAA::new();
aa.register_noalias(1, 10, 20);
let loc_a = make_memloc_with_noalias(100, 8, 1, vec![10]);
let loc_b = make_memloc_with_noalias(200, 8, 1, vec![20]);
aa.alias(&loc_a, &loc_b);
let stats = aa.get_stats();
assert_eq!(stats.total_queries, 1);
assert_eq!(stats.noalias_results, 1);
assert_eq!(stats.scoped_disambiguations, 1);
}
#[test]
fn test_memory_ssa_new() {
let mssa = X86MemorySSA::new();
assert!(mssa.live_on_entry.is_none());
assert_eq!(mssa.total_accesses(), 0);
}
#[test]
fn test_memory_ssa_build_simple() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false),
(1, 11, X86Opcode::Store, 100, 8, false),
(1, 12, X86Opcode::Load, 100, 8, true),
];
let block_order = vec![1];
mssa.build(&ops, &block_order);
assert!(mssa.live_on_entry.is_some());
assert_eq!(mssa.memory_defs.len(), 3); assert_eq!(mssa.memory_uses.len(), 1);
}
#[test]
fn test_memory_ssa_get_defining_access() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false),
(1, 11, X86Opcode::Load, 100, 8, true),
];
mssa.build(&ops, &[1]);
let defining = mssa.get_defining_access(11);
assert!(defining.is_some());
}
#[test]
fn test_memory_ssa_verify_ok() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![(1, 10, X86Opcode::Store, 100, 8, false)];
mssa.build(&ops, &[1]);
assert!(mssa.verify().is_ok());
}
#[test]
fn test_memory_ssa_identify_dead_stores() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false), (1, 11, X86Opcode::Store, 100, 8, false), (1, 12, X86Opcode::Load, 100, 8, true),
];
mssa.build(&ops, &[1]);
let dead = mssa.identify_dead_stores();
assert!(!dead.is_empty());
}
#[test]
fn test_memory_ssa_optimize_uses() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 200, 8, false), (1, 11, X86Opcode::Load, 100, 8, true),
];
mssa.build(&ops, &[1]);
mssa.optimize_uses();
assert!(mssa.is_optimized);
}
#[test]
fn test_memory_ssa_clobber_walker() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false),
(1, 11, X86Opcode::Load, 100, 8, true),
];
mssa.build(&ops, &[1]);
let access_id = mssa.get_memory_access(11);
assert!(access_id.is_some());
let loc = MemoryLocation::new(100, 8);
let clobber = mssa.get_clobbering_access(access_id.unwrap(), &loc);
assert!(clobber.is_some());
}
#[test]
fn test_memory_ssa_walk_def_chain() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false),
(1, 11, X86Opcode::Store, 100, 8, false),
];
mssa.build(&ops, &[1]);
let access_id = mssa.get_memory_access(11);
let chain = mssa.walk_def_chain(access_id.unwrap());
assert!(!chain.is_empty());
}
#[test]
fn test_memory_ssa_dominates() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let mut idom = HashMap::new();
idom.insert(1, 0);
idom.insert(2, 1);
idom.insert(3, 1);
mssa.set_dominators(idom);
assert!(mssa.dominates(1, 2));
assert!(mssa.dominates(1, 3));
assert!(!mssa.dominates(2, 3));
}
#[test]
fn test_mssa_updater_insert_use() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let mut updater = X86MemorySSAUpdater::new(mssa);
let use_id = updater.insert_use(100, 1, 42, 8);
assert!(use_id > 0);
assert!(!updater.update_log.is_empty());
}
#[test]
fn test_mssa_updater_insert_def() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let mut updater = X86MemorySSAUpdater::new(mssa);
let def_id = updater.insert_def(100, 1, 42, 8);
assert!(def_id > 0);
}
#[test]
fn test_mssa_updater_remove_access() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let access_id = mssa.get_memory_access(10);
let mut updater = X86MemorySSAUpdater::new(mssa);
assert!(updater.remove_access(access_id.unwrap()));
}
#[test]
fn test_mem_dep_new() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[]);
let mem_dep = X86MemDep::new(mssa);
assert_eq!(mem_dep.cache_size(), 0);
}
#[test]
fn test_mem_dep_get_dependency() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false),
(1, 11, X86Opcode::Load, 100, 8, true),
];
mssa.build(&ops, &[1]);
let mut mem_dep = X86MemDep::new(mssa);
let result = mem_dep.get_dependency(100, 8, 11);
assert!(
result.dep_kind == X86MemDepKind::Def || result.dep_kind == X86MemDepKind::NonLocal
);
}
#[test]
fn test_mem_dep_cache_hit() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let mut mem_dep = X86MemDep::new(mssa);
let _ = mem_dep.get_dependency(100, 8, 10);
assert_eq!(mem_dep.stats.cache_misses, 1);
let _ = mem_dep.get_dependency(100, 8, 10);
assert_eq!(mem_dep.stats.cache_hits, 1);
}
#[test]
fn test_mem_dep_invalidate_cache() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[]);
let mut mem_dep = X86MemDep::new(mssa);
let _ = mem_dep.get_dependency(100, 8, 11);
assert_eq!(mem_dep.cache_size(), 1);
mem_dep.invalidate_cache();
assert_eq!(mem_dep.cache_size(), 0);
}
#[test]
fn test_mem_dep_batch_query() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[]);
let mut mem_dep = X86MemDep::new(mssa);
mem_dep.batch_add_query(100, 8, 10);
mem_dep.batch_add_query(200, 4, 10);
assert_eq!(mem_dep.pending_batch_queries(), 2);
let results = mem_dep.flush_batch();
assert_eq!(results.len(), 2);
assert_eq!(mem_dep.pending_batch_queries(), 0);
}
#[test]
fn test_x86_alias_analysis_new() {
let aa = X86AliasAnalysis::new();
assert!(!aa.initialized);
}
#[test]
fn test_x86_alias_analysis_initialize() {
let mut aa = X86AliasAnalysis::new();
aa.initialize();
assert!(aa.initialized);
assert!(aa.mem_dep.is_some());
}
#[test]
fn test_x86_alias_analysis_alias() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_alloca(100, 8);
aa.basic_aa.register_alloca(200, 8);
let loc_a = make_memloc(100, 8);
let loc_b = make_memloc(200, 8);
let result = aa.alias(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::NoAlias);
}
#[test]
fn test_x86_alias_analysis_cache() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_alloca(100, 8);
aa.basic_aa.register_alloca(200, 8);
let loc_a = make_memloc(100, 8);
let loc_b = make_memloc(200, 8);
let _ = aa.alias(&loc_a, &loc_b);
let result = aa.alias(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::NoAlias);
assert_eq!(result.source, "cache");
}
#[test]
fn test_x86_alias_analysis_get_mod_ref_for_call() {
let mut aa = X86AliasAnalysis::new();
aa.globals_mod_ref
.register_function(100, FunctionModRefInfo::DoesNotAccessMemory);
let loc = make_memloc(500, 8);
let modref = aa.get_mod_ref_for_call(100, &loc);
assert_eq!(modref, ModRefInfo::NoModRef);
}
#[test]
fn test_x86_alias_analysis_get_points_to() {
let mut aa = X86AliasAnalysis::new();
aa.cfl_aa.register_pointer(1);
aa.cfl_aa.add_points_to(1, 42);
let pts = aa.get_points_to(1);
assert!(pts.objects.contains(&42));
}
#[test]
fn test_x86_alias_analysis_alias_sets() {
let mut aa = X86AliasAnalysis::new();
let set = aa.get_alias_set(100);
assert_eq!(set.id, 100);
aa.merge_alias_sets(100, 200);
assert!(aa.alias_sets.contains_key(&100));
assert!(aa.alias_sets.contains_key(&200));
}
#[test]
fn test_x86_alias_analysis_print_stats() {
let aa = X86AliasAnalysis::new();
let stats = aa.print_stats();
assert!(stats.contains("X86 Alias Analysis Stats"));
assert!(stats.contains("Total queries"));
}
#[test]
fn test_x86_alias_analysis_invalidate_all() {
let mut aa = X86AliasAnalysis::new();
aa.initialize();
let loc = make_memloc(100, 8);
let _ = aa.alias(&loc, &loc);
assert!(!aa.alias_cache.is_empty());
aa.invalidate_all();
assert!(aa.alias_cache.is_empty());
}
#[test]
fn test_is_memory_operation() {
assert!(is_memory_operation(X86Opcode::Load));
assert!(is_memory_operation(X86Opcode::Store));
assert!(is_memory_operation(X86Opcode::Alloca));
assert!(!is_memory_operation(X86Opcode::Add));
}
#[test]
fn test_may_read_memory() {
assert!(may_read_memory(X86Opcode::Load));
assert!(!may_read_memory(X86Opcode::Store));
assert!(may_read_memory(X86Opcode::Call));
}
#[test]
fn test_may_write_memory() {
assert!(may_write_memory(X86Opcode::Store));
assert!(!may_write_memory(X86Opcode::Load));
assert!(may_write_memory(X86Opcode::Call));
}
#[test]
fn test_get_intrinsic_mod_ref() {
assert_eq!(
get_intrinsic_mod_ref("llvm.memcpy"),
Some(ModRefInfo::ModRef)
);
assert_eq!(
get_intrinsic_mod_ref("llvm.sqrt"),
Some(ModRefInfo::NoModRef)
);
assert_eq!(get_intrinsic_mod_ref("unknown_func"), None);
}
#[test]
fn test_have_same_base_object() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_alloca(100, 8);
assert!(have_same_base_object(&aa, 100, 100));
assert!(!have_same_base_object(&aa, 100, 200));
}
#[test]
fn test_get_underlying_object() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_alloca(100, 64);
aa.basic_aa.register_gep(200, 100, 16);
assert_eq!(get_underlying_object(&aa, 100), Some(100));
assert_eq!(get_underlying_object(&aa, 200), Some(100));
assert_eq!(get_underlying_object(&aa, 999), None);
}
#[test]
fn test_get_pointer_offset() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_alloca(100, 64);
aa.basic_aa.register_gep(200, 100, 16);
assert_eq!(get_pointer_offset(&aa, 100), Some(0));
assert_eq!(get_pointer_offset(&aa, 200), Some(16));
}
#[test]
fn test_quick_alias_check() {
let loc_a = make_memloc(100, 8);
let loc_b = make_memloc(200, 8);
assert_eq!(quick_alias_check(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_build_pipeline() {
let aa = build_x86_alias_analysis_pipeline();
assert!(aa.initialized);
}
#[test]
fn test_function_mod_ref_info_display() {
assert_eq!(
format!("{}", FunctionModRefInfo::DoesNotAccessMemory),
"readnone"
);
assert_eq!(
format!("{}", FunctionModRefInfo::OnlyReadsMemory),
"readonly"
);
assert_eq!(
format!("{}", FunctionModRefInfo::OnlyAccessesInaccessibleMemory),
"inaccessiblememonly"
);
}
#[test]
fn test_null_pointer_no_alias() {
let mut basic = X86BasicAA::new();
let loc_a = make_memloc(0, 8);
let loc_b = make_memloc(100, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_same_pointer_zero_size() {
let mut basic = X86BasicAA::new();
let loc_a = make_memloc(100, 0);
let loc_b = make_memloc(100, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::MustAlias);
}
#[test]
fn test_many_allocas() {
let mut basic = X86BasicAA::new();
for i in 0..100 {
basic.register_alloca(i * 10, 8);
}
let loc_a = make_memloc(10, 8);
let loc_b = make_memloc(990, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_cfl_aa_fixpoint_convergence() {
let mut cfl = X86CFLAA::new();
cfl.register_pointer(1);
cfl.register_pointer(2);
cfl.register_pointer(3);
cfl.add_assign(1, 2);
cfl.add_assign(2, 3);
cfl.add_assign(3, 1);
cfl.add_points_to(1, 42);
cfl.solve();
assert!(cfl.get_points_to(2).objects.contains(&42));
assert!(cfl.get_points_to(3).objects.contains(&42));
}
#[test]
fn test_mem_dep_kind_display() {
assert_eq!(format!("{}", X86MemDepKind::NonLocal), "NonLocal");
assert_eq!(format!("{}", X86MemDepKind::Def), "Def");
assert_eq!(format!("{}", X86MemDepKind::Clobber), "Clobber");
assert_eq!(format!("{}", X86MemDepKind::Unknown), "Unknown");
}
#[test]
fn test_scev_display() {
let expr = X86SCEVExpr::addrec(X86SCEVExpr::constant(0), X86SCEVExpr::constant(1), 1);
assert_eq!(format!("{}", expr), "{0,+,1}<loop_1>");
}
#[test]
fn test_tbaa_tag_new() {
let tag = TBAATag::new(1, 2, 16);
assert_eq!(tag.access_type_id, 1);
assert_eq!(tag.base_type_id, 2);
assert_eq!(tag.offset, 16);
assert!(!tag.is_constant);
}
#[test]
fn test_basic_aa_gep_deep_chain() {
let mut basic = X86BasicAA::new();
basic.register_alloca(1, 1024);
let mut prev = 1u64;
for i in 0..20 {
let gep_id = 100 + i;
basic.register_gep(gep_id, prev, (i * 8) as i64);
prev = gep_id;
}
let base = basic.resolve_gep_base(119);
assert_eq!(base, Some(1));
}
#[test]
fn test_cfl_aa_many_pointers() {
let mut cfl = X86CFLAA::new();
for i in 1..=100 {
cfl.register_pointer(i);
cfl.add_points_to(i, i + 1000);
}
cfl.solve();
for i in 1..=100 {
let pts = cfl.get_points_to(i);
assert_eq!(pts.len(), 1);
assert!(pts.objects.contains(&(i + 1000)));
}
}
#[test]
fn test_memory_ssa_deep_def_chain() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let mut ops = Vec::new();
for i in 0..50 {
ops.push((1, i, X86Opcode::Store, 100, 8, false));
}
mssa.build(&ops, &[1]);
let access_id = mssa.get_memory_access(49);
let chain = mssa.walk_def_chain(access_id.unwrap());
assert!(chain.len() >= 2);
}
#[test]
fn test_alias_result_meet_symmetry() {
assert_eq!(
AliasResult::meet(AliasResult::MayAlias, AliasResult::NoAlias),
AliasResult::NoAlias
);
assert_eq!(
AliasResult::meet(AliasResult::NoAlias, AliasResult::NoAlias),
AliasResult::NoAlias
);
assert_eq!(
AliasResult::meet(AliasResult::MustAlias, AliasResult::MustAlias),
AliasResult::MustAlias
);
}
#[test]
fn test_memory_location_zero_size_has_known() {
let loc = MemoryLocation::new(100, 0);
assert!(!loc.has_known_size);
}
#[test]
fn test_points_to_set_max_size() {
let mut pts = PointsToSet::empty();
for i in 0..(X86_MAX_POINTS_TO_SET + 10) {
pts.objects.insert(i as u64);
}
assert!(pts.objects.len() > X86_MAX_POINTS_TO_SET || pts.objects.len() > 0);
}
#[test]
fn test_globals_mod_ref_unknown_function() {
let gmr = X86GlobalsModRef::new();
let loc = make_memloc(42, 8);
let result = gmr.get_mod_ref_for_call(999, &loc);
assert_eq!(result, ModRefInfo::ModRef);
}
#[test]
fn test_x86_opcode_display() {
let op = X86Opcode::Store;
assert!(!format!("{}", op).is_empty());
}
#[test]
fn test_memory_ssa_optimize_no_defs() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[1]);
mssa.optimize_uses();
assert!(mssa.is_optimized);
}
#[test]
fn test_memory_ssa_verify_empty() {
let mssa = X86MemorySSA::new();
assert!(mssa.verify().is_ok());
}
#[test]
fn test_memory_ssa_multi_block_phis() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let mut preds = HashMap::new();
preds.insert(1, vec![]);
preds.insert(2, vec![1]);
preds.insert(3, vec![1, 2]);
mssa.set_predecessors(preds);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false),
(2, 20, X86Opcode::Store, 100, 8, false),
(3, 30, X86Opcode::Load, 100, 8, true),
];
mssa.build(&ops, &[1, 2, 3]);
assert!(mssa.block_to_phi.contains_key(&3));
}
#[test]
fn test_memory_ssa_redundant_load_detection() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let ops = vec![
(1, 10, X86Opcode::Store, 100, 8, false),
(1, 11, X86Opcode::Load, 100, 8, true),
(1, 12, X86Opcode::Load, 100, 8, true), ];
mssa.build(&ops, &[1]);
let redundant = mssa.identify_redundant_loads();
assert!(!redundant.is_empty());
}
#[test]
fn test_mssa_updater_move_instruction() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let mut updater = X86MemorySSAUpdater::new(mssa);
updater.move_instruction(10, 1, 2);
assert!(!updater.update_log.is_empty());
}
#[test]
fn test_scoped_noalias_symmetric() {
let mut aa = X86ScopedNoAliasAA::new();
aa.register_noalias(1, 10, 20);
let loc_a = make_memloc_with_noalias(100, 8, 1, vec![10]);
let loc_b = make_memloc_with_noalias(200, 8, 1, vec![20]);
let loc_c = make_memloc_with_noalias(300, 8, 1, vec![10]);
let loc_d = make_memloc_with_noalias(400, 8, 1, vec![20]);
assert_eq!(aa.may_alias(&loc_a, &loc_b), AliasResult::NoAlias);
assert_eq!(aa.may_alias(&loc_c, &loc_d), AliasResult::NoAlias);
}
#[test]
fn test_basic_aa_partial_alias_exact_overlap() {
let mut basic = X86BasicAA::new();
basic.register_alloca(1, 64);
basic.register_gep(10, 1, 4);
basic.register_gep(20, 1, 4);
let loc_a = make_memloc(10, 4);
let loc_b = make_memloc(20, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::MustAlias);
}
#[test]
fn test_scev_aa_unknown_scev() {
let mut scev_aa = X86SCEVAA::new();
scev_aa.register_scev(1, X86SCEVExpr::Unknown);
scev_aa.register_scev(2, X86SCEVExpr::Unknown);
let loc_a = make_memloc(1, 8);
let loc_b = make_memloc(2, 8);
assert_eq!(scev_aa.alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_tbaa_ancestor_caching() {
let mut tbaa = X86TypeBasedAA::new();
tbaa.add_type_node(TBAANode::new(1, 0, "root"));
tbaa.add_type_node(TBAANode::new(2, 1, "middle"));
tbaa.add_type_node(TBAANode::new(3, 2, "leaf"));
let ancestors = tbaa.get_ancestors(3);
assert!(ancestors.contains(&3));
assert!(ancestors.contains(&2));
assert!(ancestors.contains(&1));
let ancestors2 = tbaa.get_ancestors(3);
assert_eq!(ancestors, ancestors2);
}
#[test]
fn test_globals_mod_ref_accessible_only() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_function(100, FunctionModRefInfo::OnlyAccessesInaccessibleMemory);
let loc = make_memloc(500, 8);
assert_eq!(gmr.get_mod_ref_for_call(100, &loc), ModRefInfo::NoModRef);
}
#[test]
fn test_globals_mod_ref_writeonly() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_function(100, FunctionModRefInfo::OnlyWritesMemory);
let loc = make_memloc(500, 8);
assert_eq!(gmr.get_mod_ref_for_call(100, &loc), ModRefInfo::ModRef);
}
#[test]
fn test_globals_mod_ref_unknown_global_modifies() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_global(42, true, false);
assert!(gmr.function_modifies_global(999, 42));
}
#[test]
fn test_globals_mod_ref_statistics_update() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_global(1, false, false);
gmr.register_global(2, false, true);
gmr.update_counts();
let stats = gmr.get_stats();
assert_eq!(stats.non_address_taken_count, 2);
assert_eq!(stats.read_only_count, 1);
}
#[test]
fn test_alias_set_access_mod_ref() {
let mut a = AliasSet::new(1);
a.access_type = AliasSetAccess::Mod;
let mut b = AliasSet::new(2);
b.access_type = AliasSetAccess::Ref;
a.merge(&b);
assert_eq!(a.access_type, AliasSetAccess::ModRef);
}
#[test]
fn test_alias_result_partial_alias() {
let mut basic = X86BasicAA::new();
let loc_a = make_memloc(100, 4);
let loc_b = make_memloc(100, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::PartialAlias);
}
#[test]
fn test_cfl_aa_chain_of_assigns() {
let mut cfl = X86CFLAA::new();
for i in 1..=5 {
cfl.register_pointer(i);
}
cfl.add_assign(2, 1);
cfl.add_assign(3, 2);
cfl.add_assign(4, 3);
cfl.add_assign(5, 4);
cfl.add_points_to(1, 42);
cfl.solve();
assert!(cfl.get_points_to(5).objects.contains(&42));
}
#[test]
fn test_mem_dep_pointer_dependency() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[1]);
let mut mem_dep = X86MemDep::new(mssa);
let result = mem_dep.get_pointer_dependency(100, 10);
assert!(matches!(
result.dep_kind,
X86MemDepKind::Unknown | X86MemDepKind::NonLocal
));
}
#[test]
fn test_alias_cache_eviction() {
let mut aa = X86AliasAnalysis::new();
aa.max_alias_cache_size = 10;
for i in 0..20 {
aa.basic_aa.register_alloca(i * 10, 8);
}
for i in 0..15 {
let loc_a = make_memloc(i * 10, 8);
let loc_b = make_memloc(i * 10 + 5, 8);
let _ = aa.alias(&loc_a, &loc_b);
}
assert!(aa.alias_cache.len() <= aa.max_alias_cache_size + 5);
}
#[test]
fn test_x86_alias_result_with_explanation() {
let result =
X86AliasResult::new(AliasResult::NoAlias, "Test").with_explanation("Because reasons");
assert_eq!(result.result, AliasResult::NoAlias);
assert_eq!(result.source, "Test");
assert_eq!(result.explanation, Some("Because reasons".to_string()));
}
#[test]
fn test_mod_ref_info_top_bottom() {
assert_eq!(ModRefInfo::top(), ModRefInfo::ModRef);
assert_eq!(ModRefInfo::bottom(), ModRefInfo::NoModRef);
}
#[test]
fn test_alias_result_top_bottom() {
assert_eq!(AliasResult::top(), AliasResult::MayAlias);
assert_eq!(AliasResult::bottom(), AliasResult::NoAlias);
}
}
#[derive(Debug, Clone)]
pub enum X86SCEVTripCount {
Exact(u64),
UpperBound(u64),
LowerBound(u64),
Bounded(u64, u64),
Unknown,
}
impl X86SCEVTripCount {
pub fn max(&self) -> Option<u64> {
match self {
X86SCEVTripCount::Exact(v) => Some(*v),
X86SCEVTripCount::UpperBound(v) => Some(*v),
X86SCEVTripCount::Bounded(_lo, hi) => Some(*hi),
X86SCEVTripCount::LowerBound(_) => None,
X86SCEVTripCount::Unknown => None,
}
}
pub fn min(&self) -> Option<u64> {
match self {
X86SCEVTripCount::Exact(v) => Some(*v),
X86SCEVTripCount::LowerBound(v) => Some(*v),
X86SCEVTripCount::Bounded(lo, _) => Some(*lo),
X86SCEVTripCount::UpperBound(_) => None,
X86SCEVTripCount::Unknown => None,
}
}
pub fn is_exact(&self) -> bool {
matches!(self, X86SCEVTripCount::Exact(_))
}
pub fn is_unknown(&self) -> bool {
matches!(self, X86SCEVTripCount::Unknown)
}
}
impl fmt::Display for X86SCEVTripCount {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
X86SCEVTripCount::Exact(v) => write!(f, "exact({})", v),
X86SCEVTripCount::UpperBound(v) => write!(f, "<={}", v),
X86SCEVTripCount::LowerBound(v) => write!(f, ">={}", v),
X86SCEVTripCount::Bounded(lo, hi) => write!(f, "bounded({}, {})", lo, hi),
X86SCEVTripCount::Unknown => write!(f, "unknown"),
}
}
}
#[derive(Debug, Clone)]
pub struct X86SCEVTripCounter {
pub scev_map: HashMap<u64, X86SCEVExpr>,
pub loop_max_trips: HashMap<u64, u64>,
}
impl X86SCEVTripCounter {
pub fn new() -> Self {
X86SCEVTripCounter {
scev_map: HashMap::new(),
loop_max_trips: HashMap::new(),
}
}
pub fn compute_trip_count(
&self,
start: i64,
step: i64,
limit: i64,
is_signed: bool,
) -> X86SCEVTripCount {
if step == 0 {
if start < limit {
return X86SCEVTripCount::Unknown;
}
return X86SCEVTripCount::Exact(0);
}
if is_signed {
self.compute_signed_trip_count(start, step, limit)
} else {
self.compute_unsigned_trip_count(start as u64, step as u64, limit as u64)
}
}
fn compute_signed_trip_count(&self, start: i64, step: i64, limit: i64) -> X86SCEVTripCount {
if step > 0 {
if start >= limit {
return X86SCEVTripCount::Exact(0);
}
let diff = (limit - start) as u64;
let step_u = step as u64;
let trips = (diff + step_u - 1) / step_u;
if diff % step_u == 0 {
X86SCEVTripCount::Exact(trips)
} else {
X86SCEVTripCount::UpperBound(trips)
}
} else {
if start <= limit {
return X86SCEVTripCount::Exact(0);
}
let diff = (start - limit) as u64;
let step_abs = (-step) as u64;
let trips = (diff + step_abs - 1) / step_abs;
if diff % step_abs == 0 {
X86SCEVTripCount::Exact(trips)
} else {
X86SCEVTripCount::UpperBound(trips)
}
}
}
fn compute_unsigned_trip_count(&self, start: u64, step: u64, limit: u64) -> X86SCEVTripCount {
if step == 0 {
if start < limit {
return X86SCEVTripCount::Unknown;
}
return X86SCEVTripCount::Exact(0);
}
if start >= limit {
return X86SCEVTripCount::Exact(0);
}
let diff = limit - start;
let trips = (diff + step - 1) / step;
if diff % step == 0 {
X86SCEVTripCount::Exact(trips)
} else {
X86SCEVTripCount::UpperBound(trips)
}
}
pub fn estimate_from_addrec(&self, addrec: &X86SCEVExpr) -> X86SCEVTripCount {
if let X86SCEVExpr::AddRec {
base,
step,
loop_id,
} = addrec
{
if let (X86SCEVExpr::Constant(base_val), X86SCEVExpr::Constant(step_val)) =
(base.as_ref(), step.as_ref())
{
if let Some(max_trips) = self.loop_max_trips.get(loop_id) {
return X86SCEVTripCount::UpperBound(*max_trips);
}
let max_val = base_val
.checked_add(
step_val
.checked_mul(X86_SCEV_MAX_BACKEDGE_TAKEN_COUNT as i64)
.unwrap_or(i64::MAX),
)
.unwrap_or(i64::MAX);
if *step_val > 0 {
self.compute_signed_trip_count(*base_val, *step_val, max_val)
} else {
self.compute_signed_trip_count(*base_val, *step_val, -max_val)
}
} else {
X86SCEVTripCount::Unknown
}
} else {
X86SCEVTripCount::Unknown
}
}
pub fn register_loop(&mut self, loop_id: u64, max_trips: u64) {
self.loop_max_trips.insert(loop_id, max_trips);
}
}
impl Default for X86SCEVTripCounter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86StratifiedCFLAA {
pub base: X86CFLAA,
pub field_points_to: HashMap<(u64, u64), PointsToSet>,
pub struct_layouts: HashMap<(u64, u64), u64>,
pub max_depth: usize,
pub stats: X86StratifiedCFLAAStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86StratifiedCFLAAStats {
pub total_field_queries: u64,
pub field_noalias: u64,
pub field_mustalias: u64,
}
impl X86StratifiedCFLAA {
pub fn new(max_depth: usize) -> Self {
X86StratifiedCFLAA {
base: X86CFLAA::new(),
field_points_to: HashMap::new(),
struct_layouts: HashMap::new(),
max_depth,
stats: X86StratifiedCFLAAStats::default(),
}
}
pub fn register_field(&mut self, base_type_id: u64, field_offset: u64, field_type_id: u64) {
self.struct_layouts
.insert((base_type_id, field_offset), field_type_id);
}
pub fn add_field_points_to(&mut self, ptr: u64, field_offset: u64, obj: u64) {
self.field_points_to
.entry((ptr, field_offset))
.or_insert_with(PointsToSet::empty)
.objects
.insert(obj);
}
pub fn field_may_alias(
&mut self,
ptr_a: u64,
offset_a: u64,
size_a: u64,
ptr_b: u64,
offset_b: u64,
size_b: u64,
) -> AliasResult {
self.stats.total_field_queries += 1;
if ptr_a == ptr_b && ptr_a != 0 {
if offset_a == offset_b && size_a == size_b {
self.stats.field_mustalias += 1;
return AliasResult::MustAlias;
}
let end_a = offset_a + size_a;
let end_b = offset_b + size_b;
if end_a <= offset_b || end_b <= offset_a {
self.stats.field_noalias += 1;
return AliasResult::NoAlias;
}
return AliasResult::PartialAlias;
}
let pts_a = self
.field_points_to
.get(&(ptr_a, offset_a))
.cloned()
.unwrap_or_else(PointsToSet::empty);
let pts_b = self
.field_points_to
.get(&(ptr_b, offset_b))
.cloned()
.unwrap_or_else(PointsToSet::empty);
if pts_a.is_noalias || pts_b.is_noalias {
self.stats.field_noalias += 1;
return AliasResult::NoAlias;
}
if pts_a.may_alias(&pts_b) {
AliasResult::MayAlias
} else {
self.stats.field_noalias += 1;
AliasResult::NoAlias
}
}
pub fn get_field_type(&self, base_type_id: u64, offset: u64) -> Option<u64> {
self.struct_layouts.get(&(base_type_id, offset)).copied()
}
}
#[derive(Debug, Clone)]
pub struct X86MemorySSAPlacer {
pub idom: HashMap<u64, u64>,
pub dom_children: HashMap<u64, Vec<u64>>,
pub df: HashMap<u64, HashSet<u64>>,
idf_cache: HashMap<u64, HashSet<u64>>,
entry_block: u64,
}
impl X86MemorySSAPlacer {
pub fn new(idom: HashMap<u64, u64>, entry_block: u64) -> Self {
let mut dom_children: HashMap<u64, Vec<u64>> = HashMap::new();
for (&node, &idom_val) in &idom {
if node != entry_block && idom_val != 0 {
dom_children.entry(idom_val).or_default().push(node);
}
}
let mut placer = X86MemorySSAPlacer {
idom,
dom_children,
df: HashMap::new(),
idf_cache: HashMap::new(),
entry_block,
};
placer.compute_df();
placer
}
fn compute_df(&mut self) {
let all_blocks: Vec<u64> = self.idom.keys().copied().collect();
for &block in &all_blocks {
self.df.entry(block).or_insert_with(HashSet::new);
}
for &block in &all_blocks {
if block == self.entry_block {
continue;
}
let mut runner = block;
if let Some(&idom_block) = self.idom.get(&block) {
while runner != idom_block && runner != 0 {
self.df.entry(runner).or_default().insert(block);
runner = self.idom.get(&runner).copied().unwrap_or(0);
if runner == 0 {
break;
}
}
}
}
}
pub fn iterated_dominance_frontier(&mut self, blocks: &HashSet<u64>) -> HashSet<u64> {
let key: u64 = blocks
.iter()
.fold(0, |acc, b| acc.wrapping_mul(31).wrapping_add(*b));
if let Some(cached) = self.idf_cache.get(&key) {
return cached.clone();
}
let mut idf = blocks.clone();
let mut changed = true;
while changed {
changed = false;
let current: Vec<u64> = idf.iter().copied().collect();
for &block in ¤t {
if let Some(df_set) = self.df.get(&block) {
for &df_block in df_set {
if idf.insert(df_block) {
changed = true;
}
}
}
}
}
self.idf_cache.insert(key, idf.clone());
idf
}
pub fn place_phis(&mut self, def_blocks: &HashSet<u64>) -> HashSet<u64> {
self.iterated_dominance_frontier(def_blocks)
}
pub fn get_df(&self, block: u64) -> HashSet<u64> {
self.df.get(&block).cloned().unwrap_or_default()
}
pub fn dominates(&self, block_a: u64, block_b: u64) -> bool {
if block_a == block_b {
return true;
}
let mut current = block_b;
for _ in 0..X86_MEMSSA_MAX_WALK_DEPTH {
if current == block_a {
return true;
}
match self.idom.get(¤t) {
Some(&idom_val) if idom_val != current && idom_val != 0 => {
current = idom_val;
}
_ => break,
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct X86AliasEvaluator {
pub ground_truth: HashMap<(u64, u64, u64, u64), AliasResult>,
pub results: Vec<X86EvalRecord>,
pub summary: X86EvalSummary,
}
#[derive(Debug, Clone)]
pub struct X86EvalRecord {
pub query: (u64, u64, u64, u64),
pub expected: AliasResult,
pub actual: AliasResult,
pub source: String,
pub correct: bool,
}
#[derive(Debug, Clone, Default)]
pub struct X86EvalSummary {
pub total_queries: u64,
pub correct_noalias: u64,
pub correct_mustalias: u64,
pub correct_partialalias: u64,
pub correct_mayalias: u64,
pub incorrect: u64,
pub precision: f64,
pub soundness: f64,
}
impl X86AliasEvaluator {
pub fn new() -> Self {
X86AliasEvaluator {
ground_truth: HashMap::new(),
results: Vec::new(),
summary: X86EvalSummary::default(),
}
}
pub fn add_ground_truth(
&mut self,
ptr_a: u64,
size_a: u64,
ptr_b: u64,
size_b: u64,
expected: AliasResult,
) {
self.ground_truth
.insert((ptr_a, size_a, ptr_b, size_b), expected);
}
pub fn evaluate(
&mut self,
ptr_a: u64,
size_a: u64,
ptr_b: u64,
size_b: u64,
actual: X86AliasResult,
) {
let expected = self
.ground_truth
.get(&(ptr_a, size_a, ptr_b, size_b))
.copied()
.unwrap_or(AliasResult::MayAlias);
let correct = self.check_correctness(expected, actual.result);
self.results.push(X86EvalRecord {
query: (ptr_a, size_a, ptr_b, size_b),
expected,
actual: actual.result,
source: actual.source,
correct,
});
}
fn check_correctness(&self, expected: AliasResult, actual: AliasResult) -> bool {
match (expected, actual) {
(AliasResult::MustAlias, AliasResult::MustAlias) => true,
(AliasResult::MustAlias, AliasResult::NoAlias) => false,
(AliasResult::MustAlias, _) => false,
(AliasResult::NoAlias, AliasResult::NoAlias) => true,
(AliasResult::NoAlias, _) => true,
(AliasResult::PartialAlias, AliasResult::PartialAlias) => true,
(AliasResult::PartialAlias, AliasResult::NoAlias) => false,
(AliasResult::PartialAlias, _) => true,
(AliasResult::MayAlias, _) => true,
}
}
pub fn compute_summary(&mut self) {
let mut summary = X86EvalSummary::default();
summary.total_queries = self.results.len() as u64;
for record in &self.results {
if record.correct {
match record.expected {
AliasResult::NoAlias => summary.correct_noalias += 1,
AliasResult::MustAlias => summary.correct_mustalias += 1,
AliasResult::PartialAlias => summary.correct_partialalias += 1,
AliasResult::MayAlias => summary.correct_mayalias += 1,
}
} else {
summary.incorrect += 1;
}
}
let non_mayalias: u64 = self
.results
.iter()
.filter(|r| r.actual != AliasResult::MayAlias)
.count() as u64;
summary.precision = if summary.total_queries > 0 {
non_mayalias as f64 / summary.total_queries as f64
} else {
0.0
};
summary.soundness = if summary.total_queries > 0 {
(summary.total_queries - summary.incorrect) as f64 / summary.total_queries as f64
} else {
1.0
};
self.summary = summary;
}
pub fn report(&self) -> String {
let s = &self.summary;
format!(
"Alias Analysis Evaluation Report\n\
==================================\n\
Total queries: {}\n\
Correct NoAlias: {}\n\
Correct MustAlias: {}\n\
Correct PartAlias: {}\n\
Correct MayAlias: {}\n\
Incorrect: {}\n\
Precision: {:.2}%\n\
Soundness: {:.2}%\n",
s.total_queries,
s.correct_noalias,
s.correct_mustalias,
s.correct_partialalias,
s.correct_mayalias,
s.incorrect,
s.precision * 100.0,
s.soundness * 100.0,
)
}
}
impl Default for X86AliasEvaluator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86BatchAliasQuery {
pub pending: Vec<(u64, u64, u64, u64)>,
pub cache: HashMap<(u64, u64, u64, u64), AliasResult>,
pub max_batch_size: usize,
pub stats: X86BatchStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86BatchStats {
pub batches_processed: u64,
pub queries_processed: u64,
pub cache_hits: u64,
}
impl X86BatchAliasQuery {
pub fn new(max_batch_size: usize) -> Self {
X86BatchAliasQuery {
pending: Vec::new(),
cache: HashMap::new(),
max_batch_size,
stats: X86BatchStats::default(),
}
}
pub fn add(&mut self, ptr_a: u64, size_a: u64, ptr_b: u64, size_b: u64) {
self.pending.push((ptr_a, size_a, ptr_b, size_b));
}
pub fn flush<F>(&mut self, mut query_fn: F) -> Vec<AliasResult>
where
F: FnMut(u64, u64, u64, u64) -> AliasResult,
{
let mut results = Vec::with_capacity(self.pending.len());
for &(ptr_a, size_a, ptr_b, size_b) in &self.pending {
let key = (ptr_a, size_a, ptr_b, size_b);
let sym_key = (ptr_b, size_b, ptr_a, size_a);
if let Some(&cached) = self.cache.get(&key).or_else(|| self.cache.get(&sym_key)) {
self.stats.cache_hits += 1;
results.push(cached);
} else {
let result = query_fn(ptr_a, size_a, ptr_b, size_b);
self.cache.insert(key, result);
results.push(result);
}
}
self.stats.queries_processed += self.pending.len() as u64;
self.stats.batches_processed += 1;
self.pending.clear();
results
}
pub fn clear(&mut self) {
self.pending.clear();
self.cache.clear();
}
pub fn pending_count(&self) -> usize {
self.pending.len()
}
}
#[derive(Debug, Clone)]
pub struct X86AliasSetTracker {
pub sets: HashMap<u64, AliasSet>,
pub pointer_to_set: HashMap<u64, u64>,
next_set_id: u64,
pub stats: X86AliasSetTrackerStats,
}
#[derive(Debug, Clone, Default)]
pub struct X86AliasSetTrackerStats {
pub total_sets: usize,
pub total_merges: u64,
pub max_set_size: usize,
}
impl X86AliasSetTracker {
pub fn new() -> Self {
X86AliasSetTracker {
sets: HashMap::new(),
pointer_to_set: HashMap::new(),
next_set_id: 1,
stats: X86AliasSetTrackerStats::default(),
}
}
pub fn get_or_create_set(&mut self, ptr: u64) -> u64 {
if let Some(&set_id) = self.pointer_to_set.get(&ptr) {
return set_id;
}
let set_id = self.next_set_id;
self.next_set_id += 1;
let mut set = AliasSet::new(set_id);
set.add_pointer(ptr);
self.sets.insert(set_id, set);
self.pointer_to_set.insert(ptr, set_id);
self.stats.total_sets = self.sets.len();
set_id
}
pub fn merge_sets(&mut self, ptr_a: u64, ptr_b: u64) {
let set_a_id = self.get_or_create_set(ptr_a);
let set_b_id = self.get_or_create_set(ptr_b);
if set_a_id == set_b_id {
return;
}
let (keep_id, absorb_id) = {
let size_a = self
.sets
.get(&set_a_id)
.map(|s| s.pointers.len())
.unwrap_or(0);
let size_b = self
.sets
.get(&set_b_id)
.map(|s| s.pointers.len())
.unwrap_or(0);
if size_a >= size_b {
(set_a_id, set_b_id)
} else {
(set_b_id, set_a_id)
}
};
let absorb_set = self.sets[&absorb_id].clone();
let absorbed_pointers: Vec<u64> = absorb_set.pointers.iter().copied().collect();
if let Some(keep_set) = self.sets.get_mut(&keep_id) {
keep_set.merge(&absorb_set);
}
for ptr in &absorbed_pointers {
self.pointer_to_set.insert(*ptr, keep_id);
}
self.sets.remove(&absorb_id);
self.stats.total_merges += 1;
self.stats.total_sets = self.sets.len();
if let Some(keep_set) = self.sets.get(&keep_id) {
if keep_set.pointers.len() > self.stats.max_set_size {
self.stats.max_set_size = keep_set.pointers.len();
}
}
}
pub fn get_set(&self, ptr: u64) -> Option<&AliasSet> {
self.pointer_to_set
.get(&ptr)
.and_then(|set_id| self.sets.get(set_id))
}
pub fn get_all_sets(&self) -> Vec<&AliasSet> {
self.sets.values().collect()
}
pub fn get_set_id(&self, ptr: u64) -> Option<u64> {
self.pointer_to_set.get(&ptr).copied()
}
pub fn get_stats(&self) -> &X86AliasSetTrackerStats {
&self.stats
}
}
impl Default for X86AliasSetTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86CaptureTracker {
pub captured: bool,
pub captured_by_ret: bool,
pub captured_by_arg: bool,
pub captured_by_global: bool,
pub capturing_users: HashSet<u64>,
pub total_uses: usize,
}
impl X86CaptureTracker {
pub fn new() -> Self {
X86CaptureTracker {
captured: false,
captured_by_ret: false,
captured_by_arg: false,
captured_by_global: false,
capturing_users: HashSet::new(),
total_uses: 0,
}
}
pub fn mark_captured_by_ret(&mut self, user: u64) {
self.captured = true;
self.captured_by_ret = true;
self.capturing_users.insert(user);
}
pub fn mark_captured_by_arg(&mut self, user: u64) {
self.captured = true;
self.captured_by_arg = true;
self.capturing_users.insert(user);
}
pub fn mark_captured_by_global(&mut self, user: u64) {
self.captured = true;
self.captured_by_global = true;
self.capturing_users.insert(user);
}
pub fn mark_use(&mut self) {
self.total_uses += 1;
}
pub fn is_captured(&self) -> bool {
self.captured
}
pub fn is_captured_by_ret_only(&self) -> bool {
self.captured_by_ret && !self.captured_by_arg && !self.captured_by_global
}
pub fn is_not_captured(&self) -> bool {
!self.captured
}
}
impl Default for X86CaptureTracker {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86MemDepBlock {
pub mssa: X86MemorySSA,
block_cache: HashMap<(u64, u64, u64), Option<u64>>,
max_blocks: usize,
}
impl X86MemDepBlock {
pub fn new(mssa: X86MemorySSA) -> Self {
X86MemDepBlock {
mssa,
block_cache: HashMap::new(),
max_blocks: 100,
}
}
pub fn find_def_in_block(&mut self, block_id: u64, ptr: u64, size: u64) -> Option<u64> {
let cache_key = (block_id, ptr, size);
if let Some(cached) = self.block_cache.get(&cache_key) {
return *cached;
}
let mut result = None;
let mut block_defs: Vec<(u64, &X86MemoryDef)> = self
.mssa
.memory_defs
.iter()
.filter(|(_, d)| d.block_id == block_id && !d.is_live_on_entry)
.map(|(id, d)| (*id, d))
.collect();
block_defs.sort_by_key(|(id, _)| *id);
for (def_id, def) in block_defs.iter().rev() {
if def.location.ptr == ptr || self.locations_may_alias(&def.location, ptr, size) {
result = Some(*def_id);
break;
}
}
self.block_cache.insert(cache_key, result);
result
}
fn locations_may_alias(&self, loc: &MemoryLocation, ptr: u64, size: u64) -> bool {
if loc.ptr == 0 || ptr == 0 {
return false;
}
if loc.ptr == ptr {
return true;
}
true
}
pub fn find_nearest_dominating_def(
&mut self,
block_id: u64,
ptr: u64,
size: u64,
) -> Option<u64> {
if let Some(def) = self.find_def_in_block(block_id, ptr, size) {
return Some(def);
}
let mut current = block_id;
for _ in 0..self.max_blocks {
match self.mssa.idom.get(¤t) {
Some(&idom_val) if idom_val != current && idom_val != 0 => {
if let Some(def) = self.find_def_in_block(idom_val, ptr, size) {
return Some(def);
}
current = idom_val;
}
_ => break,
}
}
None
}
pub fn invalidate_cache(&mut self) {
self.block_cache.clear();
}
}
#[derive(Debug, Clone)]
pub struct X86AAQueryCache {
entries: HashMap<(u64, u64, u64, u64), (AliasResult, u64)>,
capacity: usize,
counter: u64,
}
impl X86AAQueryCache {
pub fn new(capacity: usize) -> Self {
X86AAQueryCache {
entries: HashMap::new(),
capacity,
counter: 0,
}
}
pub fn get(&mut self, ptr_a: u64, size_a: u64, ptr_b: u64, size_b: u64) -> Option<AliasResult> {
let key = (ptr_a, size_a, ptr_b, size_b);
let sym_key = (ptr_b, size_b, ptr_a, size_a);
self.counter += 1;
if let Some((result, cnt)) = self.entries.get_mut(&key) {
*cnt = self.counter;
return Some(*result);
}
if let Some((result, cnt)) = self.entries.get_mut(&sym_key) {
*cnt = self.counter;
return Some(*result);
}
None
}
pub fn insert(
&mut self,
ptr_a: u64,
size_a: u64,
ptr_b: u64,
size_b: u64,
result: AliasResult,
) {
if self.capacity == 0 {
return;
}
self.counter += 1;
let key = (ptr_a, size_a, ptr_b, size_b);
if self.entries.len() >= self.capacity {
let mut min_counter = u64::MAX;
let mut min_key = None;
for (k, (_, cnt)) in &self.entries {
if *cnt < min_counter {
min_counter = *cnt;
min_key = Some(*k);
}
}
if let Some(k) = min_key {
self.entries.remove(&k);
}
}
self.entries.insert(key, (result, self.counter));
}
pub fn clear(&mut self) {
self.entries.clear();
self.counter = 0;
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[derive(Debug, Clone)]
pub struct X86PointsToAnalysis {
pub cfl: X86CFLAA,
pub stratified: Option<X86StratifiedCFLAA>,
pub cache: X86AAQueryCache,
pub field_sensitive: bool,
}
impl X86PointsToAnalysis {
pub fn new(field_sensitive: bool) -> Self {
X86PointsToAnalysis {
cfl: X86CFLAA::new(),
stratified: if field_sensitive {
Some(X86StratifiedCFLAA::new(8))
} else {
None
},
cache: X86AAQueryCache::new(1024),
field_sensitive,
}
}
pub fn may_alias(
&mut self,
ptr_a: u64,
offset_a: u64,
size_a: u64,
ptr_b: u64,
offset_b: u64,
size_b: u64,
) -> AliasResult {
if let Some(cached) = self.cache.get(ptr_a, size_a, ptr_b, size_b) {
return cached;
}
let result = if self.field_sensitive {
if let Some(ref mut strat) = self.stratified {
strat.field_may_alias(ptr_a, offset_a, size_a, ptr_b, offset_b, size_b)
} else {
self.cfl.may_alias(ptr_a, ptr_b)
}
} else {
self.cfl.may_alias(ptr_a, ptr_b)
};
self.cache.insert(ptr_a, size_a, ptr_b, size_b, result);
result
}
pub fn solve(&mut self) {
self.cfl.solve();
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
#[test]
fn test_trip_count_signed_positive_step() {
let counter = X86SCEVTripCounter::new();
let tc = counter.compute_trip_count(0, 1, 100, true);
assert_eq!(tc.max(), Some(100));
assert_eq!(tc.min(), Some(100));
assert!(tc.is_exact());
}
#[test]
fn test_trip_count_signed_negative_step() {
let counter = X86SCEVTripCounter::new();
let tc = counter.compute_trip_count(100, -1, 0, true);
assert_eq!(tc.max(), Some(100));
assert!(tc.is_exact());
}
#[test]
fn test_trip_count_zero_iterations() {
let counter = X86SCEVTripCounter::new();
let tc = counter.compute_trip_count(100, 1, 50, true);
assert_eq!(tc.max(), Some(0));
assert!(tc.is_exact());
}
#[test]
fn test_trip_count_unsigned() {
let counter = X86SCEVTripCounter::new();
let tc = counter.compute_trip_count(0, 2, 100, false);
assert_eq!(tc.max(), Some(50));
assert!(tc.is_exact());
}
#[test]
fn test_trip_count_unsigned_no_iterations() {
let counter = X86SCEVTripCounter::new();
let tc = counter.compute_trip_count(100, 1, 50, false);
assert_eq!(tc.max(), Some(0));
}
#[test]
fn test_trip_count_display_formats() {
assert_eq!(format!("{}", X86SCEVTripCount::Exact(42)), "exact(42)");
assert_eq!(format!("{}", X86SCEVTripCount::UpperBound(100)), "<=100");
assert_eq!(format!("{}", X86SCEVTripCount::LowerBound(10)), ">=10");
assert_eq!(
format!("{}", X86SCEVTripCount::Bounded(5, 50)),
"bounded(5, 50)"
);
assert_eq!(format!("{}", X86SCEVTripCount::Unknown), "unknown");
}
#[test]
fn test_trip_count_bounded() {
let tc = X86SCEVTripCount::Bounded(10, 100);
assert_eq!(tc.max(), Some(100));
assert_eq!(tc.min(), Some(10));
assert!(!tc.is_exact());
assert!(!tc.is_unknown());
}
#[test]
fn test_trip_count_lower_bound() {
let tc = X86SCEVTripCount::LowerBound(5);
assert_eq!(tc.min(), Some(5));
assert_eq!(tc.max(), None);
}
#[test]
fn test_trip_counter_register_loop() {
let mut counter = X86SCEVTripCounter::new();
counter.register_loop(1, 42);
assert_eq!(counter.loop_max_trips.get(&1), Some(&42));
}
#[test]
fn test_trip_counter_estimate_from_addrec_known_loop() {
let mut counter = X86SCEVTripCounter::new();
counter.register_loop(1, 10);
let addrec = X86SCEVExpr::addrec(X86SCEVExpr::constant(0), X86SCEVExpr::constant(1), 1);
let tc = counter.estimate_from_addrec(&addrec);
assert_eq!(tc.max(), Some(10));
}
#[test]
fn test_trip_counter_estimate_unknown_addrec() {
let counter = X86SCEVTripCounter::new();
let tc = counter.estimate_from_addrec(&X86SCEVExpr::Unknown);
assert!(tc.is_unknown());
}
#[test]
fn test_trip_count_signed_non_exact() {
let counter = X86SCEVTripCounter::new();
let tc = counter.compute_trip_count(0, 3, 100, true);
assert_eq!(tc.max(), Some(34));
assert!(!tc.is_exact()); }
#[test]
fn test_stratified_cfl_new() {
let scfl = X86StratifiedCFLAA::new(4);
assert_eq!(scfl.max_depth, 4);
assert!(scfl.field_points_to.is_empty());
}
#[test]
fn test_stratified_field_may_alias_same_offset() {
let mut scfl = X86StratifiedCFLAA::new(4);
scfl.add_field_points_to(1, 0, 100);
assert_eq!(
scfl.field_may_alias(1, 0, 8, 1, 0, 8),
AliasResult::MustAlias
);
}
#[test]
fn test_stratified_field_no_alias_different_offsets() {
let mut scfl = X86StratifiedCFLAA::new(4);
assert_eq!(
scfl.field_may_alias(1, 0, 8, 1, 16, 8),
AliasResult::NoAlias
);
}
#[test]
fn test_stratified_field_partial_alias() {
let mut scfl = X86StratifiedCFLAA::new(4);
assert_eq!(
scfl.field_may_alias(1, 0, 8, 1, 4, 8),
AliasResult::PartialAlias
);
}
#[test]
fn test_stratified_register_field() {
let mut scfl = X86StratifiedCFLAA::new(4);
scfl.register_field(1, 0, 10);
scfl.register_field(1, 8, 20);
assert_eq!(scfl.get_field_type(1, 0), Some(10));
assert_eq!(scfl.get_field_type(1, 8), Some(20));
assert_eq!(scfl.get_field_type(1, 16), None);
}
#[test]
fn test_stratified_different_pointers_field() {
let mut scfl = X86StratifiedCFLAA::new(4);
scfl.add_field_points_to(1, 0, 100);
scfl.add_field_points_to(2, 0, 200);
assert_eq!(scfl.field_may_alias(1, 0, 8, 2, 0, 8), AliasResult::NoAlias);
}
#[test]
fn test_stratified_noalias_flag() {
let mut scfl = X86StratifiedCFLAA::new(4);
let mut noalias_pts = PointsToSet::empty();
noalias_pts.is_noalias = true;
scfl.field_points_to.insert((1, 0), noalias_pts);
assert_eq!(scfl.field_may_alias(1, 0, 8, 2, 0, 8), AliasResult::NoAlias);
}
#[test]
fn test_stratified_stats_tracking() {
let mut scfl = X86StratifiedCFLAA::new(4);
scfl.field_may_alias(1, 0, 8, 1, 0, 8);
scfl.field_may_alias(1, 0, 8, 1, 16, 8);
assert_eq!(scfl.stats.total_field_queries, 2);
assert_eq!(scfl.stats.field_mustalias, 1);
assert_eq!(scfl.stats.field_noalias, 1);
}
#[test]
fn test_mssa_placer_new() {
let mut idom = HashMap::new();
idom.insert(1, 0);
idom.insert(2, 1);
idom.insert(3, 1);
let placer = X86MemorySSAPlacer::new(idom, 1);
assert_eq!(placer.entry_block, 1);
}
#[test]
fn test_mssa_placer_dominates() {
let mut idom = HashMap::new();
idom.insert(1, 0);
idom.insert(2, 1);
idom.insert(3, 2);
let placer = X86MemorySSAPlacer::new(idom, 1);
assert!(placer.dominates(1, 2));
assert!(placer.dominates(1, 3));
assert!(placer.dominates(2, 3));
assert!(!placer.dominates(2, 1));
assert!(!placer.dominates(3, 1));
}
#[test]
fn test_mssa_placer_self_dominates() {
let mut idom = HashMap::new();
idom.insert(1, 0);
let placer = X86MemorySSAPlacer::new(idom, 1);
assert!(placer.dominates(1, 1));
}
#[test]
fn test_mssa_placer_get_df() {
let mut idom = HashMap::new();
idom.insert(1, 0);
idom.insert(2, 1);
let placer = X86MemorySSAPlacer::new(idom, 1);
let df = placer.get_df(2);
assert!(df.is_empty() || !df.is_empty());
}
#[test]
fn test_mssa_placer_idf_empty() {
let mut idom = HashMap::new();
idom.insert(1, 0);
let mut placer = X86MemorySSAPlacer::new(idom, 1);
let blocks = HashSet::new();
let idf = placer.iterated_dominance_frontier(&blocks);
assert!(idf.is_empty());
}
#[test]
fn test_mssa_placer_place_phis_single_block() {
let mut idom = HashMap::new();
idom.insert(1, 0);
let mut placer = X86MemorySSAPlacer::new(idom, 1);
let mut defs = HashSet::new();
defs.insert(1);
let phi_blocks = placer.place_phis(&defs);
assert!(phi_blocks.contains(&1));
}
#[test]
fn test_evaluator_new() {
let eval = X86AliasEvaluator::new();
assert!(eval.ground_truth.is_empty());
assert!(eval.results.is_empty());
}
#[test]
fn test_evaluator_add_and_evaluate_correct() {
let mut eval = X86AliasEvaluator::new();
eval.add_ground_truth(1, 8, 2, 8, AliasResult::NoAlias);
let result = X86AliasResult::new(AliasResult::NoAlias, "test");
eval.evaluate(1, 8, 2, 8, result);
assert_eq!(eval.results.len(), 1);
assert!(eval.results[0].correct);
}
#[test]
fn test_evaluator_unsound_result() {
let mut eval = X86AliasEvaluator::new();
eval.add_ground_truth(1, 8, 1, 8, AliasResult::MustAlias);
let result = X86AliasResult::new(AliasResult::NoAlias, "test");
eval.evaluate(1, 8, 1, 8, result);
assert!(!eval.results[0].correct);
}
#[test]
fn test_evaluator_conservative_is_sound() {
let mut eval = X86AliasEvaluator::new();
eval.add_ground_truth(1, 8, 2, 8, AliasResult::MustAlias);
let result = X86AliasResult::new(AliasResult::MayAlias, "test");
eval.evaluate(1, 8, 2, 8, result);
assert!(eval.results[0].correct); }
#[test]
fn test_evaluator_summary_all_correct() {
let mut eval = X86AliasEvaluator::new();
eval.add_ground_truth(1, 8, 2, 8, AliasResult::NoAlias);
eval.add_ground_truth(3, 8, 4, 8, AliasResult::MustAlias);
eval.evaluate(1, 8, 2, 8, X86AliasResult::new(AliasResult::NoAlias, "t"));
eval.evaluate(3, 8, 4, 8, X86AliasResult::new(AliasResult::MustAlias, "t"));
eval.compute_summary();
assert_eq!(eval.summary.total_queries, 2);
assert_eq!(eval.summary.incorrect, 0);
assert_eq!(eval.summary.soundness, 1.0);
}
#[test]
fn test_evaluator_precision_zero_when_all_mayalias() {
let mut eval = X86AliasEvaluator::new();
eval.add_ground_truth(1, 8, 2, 8, AliasResult::NoAlias);
eval.evaluate(1, 8, 2, 8, X86AliasResult::new(AliasResult::MayAlias, "t"));
eval.compute_summary();
assert_eq!(eval.summary.precision, 0.0);
assert_eq!(eval.summary.soundness, 1.0);
}
#[test]
fn test_evaluator_precision_full_when_all_noalias() {
let mut eval = X86AliasEvaluator::new();
eval.add_ground_truth(1, 8, 2, 8, AliasResult::NoAlias);
eval.evaluate(1, 8, 2, 8, X86AliasResult::new(AliasResult::NoAlias, "t"));
eval.compute_summary();
assert_eq!(eval.summary.precision, 1.0);
}
#[test]
fn test_evaluator_report_contents() {
let eval = X86AliasEvaluator::new();
let report = eval.report();
assert!(report.contains("Alias Analysis Evaluation Report"));
assert!(report.contains("Precision"));
assert!(report.contains("Soundness"));
}
#[test]
fn test_evaluator_multiple_queries_all_correct() {
let mut eval = X86AliasEvaluator::new();
for i in 0..100 {
eval.add_ground_truth(i, 8, i + 100, 8, AliasResult::NoAlias);
}
for i in 0..100 {
eval.evaluate(
i,
8,
i + 100,
8,
X86AliasResult::new(AliasResult::NoAlias, "test"),
);
}
eval.compute_summary();
assert_eq!(eval.summary.total_queries, 100);
assert_eq!(eval.summary.incorrect, 0);
assert_eq!(eval.summary.correct_noalias, 100);
}
#[test]
fn test_batch_query_new() {
let batch = X86BatchAliasQuery::new(10);
assert_eq!(batch.max_batch_size, 10);
assert!(batch.pending.is_empty());
}
#[test]
fn test_batch_query_add_and_flush() {
let mut batch = X86BatchAliasQuery::new(10);
batch.add(1, 8, 2, 8);
batch.add(3, 4, 4, 4);
let results = batch.flush(|a, _sa, b, _sb| {
if a == b {
AliasResult::MustAlias
} else {
AliasResult::NoAlias
}
});
assert_eq!(results.len(), 2);
assert_eq!(batch.pending_count(), 0);
}
#[test]
fn test_batch_query_cache_hit() {
let mut batch = X86BatchAliasQuery::new(10);
batch.add(1, 8, 2, 8);
batch.flush(|_, _, _, _| AliasResult::NoAlias);
batch.add(1, 8, 2, 8);
batch.flush(|_, _, _, _| AliasResult::MayAlias);
assert_eq!(batch.stats.cache_hits, 1);
}
#[test]
fn test_batch_query_symmetric_cache() {
let mut batch = X86BatchAliasQuery::new(10);
batch.add(1, 8, 2, 8);
batch.flush(|_, _, _, _| AliasResult::NoAlias);
batch.add(2, 8, 1, 8);
batch.flush(|_, _, _, _| AliasResult::MayAlias);
assert_eq!(batch.stats.cache_hits, 1);
}
#[test]
fn test_batch_query_clear() {
let mut batch = X86BatchAliasQuery::new(10);
batch.add(1, 8, 2, 8);
batch.clear();
assert!(batch.pending.is_empty());
assert!(batch.cache.is_empty());
}
#[test]
fn test_batch_query_stats() {
let mut batch = X86BatchAliasQuery::new(10);
batch.add(1, 8, 2, 8);
batch.add(3, 4, 4, 4);
batch.flush(|_, _, _, _| AliasResult::MayAlias);
assert_eq!(batch.stats.queries_processed, 2);
assert_eq!(batch.stats.batches_processed, 1);
}
#[test]
fn test_tracker_new() {
let tracker = X86AliasSetTracker::new();
assert_eq!(tracker.sets.len(), 0);
}
#[test]
fn test_tracker_get_or_create() {
let mut tracker = X86AliasSetTracker::new();
let set_id = tracker.get_or_create_set(100);
assert!(set_id > 0);
assert_eq!(tracker.sets.len(), 1);
}
#[test]
fn test_tracker_same_id_for_same_pointer() {
let mut tracker = X86AliasSetTracker::new();
let id1 = tracker.get_or_create_set(100);
let id2 = tracker.get_or_create_set(100);
assert_eq!(id1, id2);
}
#[test]
fn test_tracker_merge_sets_reduces_count() {
let mut tracker = X86AliasSetTracker::new();
tracker.get_or_create_set(100);
tracker.get_or_create_set(200);
assert_eq!(tracker.sets.len(), 2);
tracker.merge_sets(100, 200);
assert_eq!(tracker.sets.len(), 1);
assert_eq!(tracker.stats.total_merges, 1);
}
#[test]
fn test_tracker_same_set_id_after_merge() {
let mut tracker = X86AliasSetTracker::new();
tracker.merge_sets(100, 200);
let id_a = tracker.get_set_id(100);
let id_b = tracker.get_set_id(200);
assert_eq!(id_a, id_b);
}
#[test]
fn test_tracker_get_set_returns_set() {
let mut tracker = X86AliasSetTracker::new();
tracker.get_or_create_set(100);
let set = tracker.get_set(100);
assert!(set.is_some());
assert!(set.unwrap().pointers.contains(&100));
}
#[test]
fn test_tracker_get_set_nonexistent() {
let tracker = X86AliasSetTracker::new();
assert!(tracker.get_set(999).is_none());
}
#[test]
fn test_tracker_all_sets() {
let mut tracker = X86AliasSetTracker::new();
tracker.get_or_create_set(100);
tracker.get_or_create_set(200);
tracker.get_or_create_set(300);
let all = tracker.get_all_sets();
assert_eq!(all.len(), 3);
}
#[test]
fn test_tracker_max_set_size_updates() {
let mut tracker = X86AliasSetTracker::new();
for i in 1..=20 {
tracker.merge_sets(100, i * 10);
}
let stats = tracker.get_stats();
assert!(stats.max_set_size >= 20);
}
#[test]
fn test_tracker_merge_same_set_noop() {
let mut tracker = X86AliasSetTracker::new();
tracker.merge_sets(100, 200);
let merges_before = tracker.stats.total_merges;
tracker.merge_sets(100, 200);
assert_eq!(tracker.stats.total_merges, merges_before);
}
#[test]
fn test_tracker_merge_chain() {
let mut tracker = X86AliasSetTracker::new();
tracker.merge_sets(1, 2);
tracker.merge_sets(2, 3);
tracker.merge_sets(3, 4);
let id1 = tracker.get_set_id(1);
let id4 = tracker.get_set_id(4);
assert_eq!(id1, id4);
assert_eq!(tracker.sets.len(), 1);
}
#[test]
fn test_capture_tracker_new_not_captured() {
let ct = X86CaptureTracker::new();
assert!(!ct.is_captured());
assert!(ct.is_not_captured());
}
#[test]
fn test_capture_tracker_captured_by_ret() {
let mut ct = X86CaptureTracker::new();
ct.mark_captured_by_ret(10);
assert!(ct.is_captured());
assert!(ct.is_captured_by_ret_only());
}
#[test]
fn test_capture_tracker_captured_by_arg_not_ret_only() {
let mut ct = X86CaptureTracker::new();
ct.mark_captured_by_ret(1);
ct.mark_captured_by_arg(2);
assert!(ct.is_captured());
assert!(!ct.is_captured_by_ret_only());
}
#[test]
fn test_capture_tracker_captured_by_global() {
let mut ct = X86CaptureTracker::new();
ct.mark_captured_by_global(1);
assert!(ct.is_captured());
assert!(!ct.is_captured_by_ret_only());
}
#[test]
fn test_capture_tracker_uses_dont_capture() {
let mut ct = X86CaptureTracker::new();
ct.mark_use();
ct.mark_use();
ct.mark_use();
assert_eq!(ct.total_uses, 3);
assert!(!ct.is_captured());
}
#[test]
fn test_capture_tracker_users_tracking() {
let mut ct = X86CaptureTracker::new();
ct.mark_captured_by_ret(10);
ct.mark_captured_by_arg(20);
assert!(ct.capturing_users.contains(&10));
assert!(ct.capturing_users.contains(&20));
assert_eq!(ct.capturing_users.len(), 2);
}
#[test]
fn test_mem_dep_block_new() {
let mssa = X86MemorySSA::new();
let mdb = X86MemDepBlock::new(mssa);
assert_eq!(mdb.max_blocks, 100);
}
#[test]
fn test_mem_dep_block_find_def_returns_some() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let mut mdb = X86MemDepBlock::new(mssa);
let def = mdb.find_def_in_block(1, 100, 8);
assert!(def.is_some());
}
#[test]
fn test_mem_dep_block_find_def_empty_block() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[1]);
let mut mdb = X86MemDepBlock::new(mssa);
let def = mdb.find_def_in_block(1, 100, 8);
assert!(def.is_none());
}
#[test]
fn test_mem_dep_block_cache_works() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let mut mdb = X86MemDepBlock::new(mssa);
let def1 = mdb.find_def_in_block(1, 100, 8);
let def2 = mdb.find_def_in_block(1, 100, 8);
assert_eq!(def1, def2);
}
#[test]
fn test_mem_dep_block_invalidate_clears_cache() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[1]);
let mut mdb = X86MemDepBlock::new(mssa);
let _ = mdb.find_def_in_block(1, 100, 8);
mdb.invalidate_cache();
assert!(mdb.block_cache.is_empty());
}
#[test]
fn test_mem_dep_block_find_nearest_dominating() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let mut idom = HashMap::new();
idom.insert(1, 0);
idom.insert(2, 1);
mssa.set_dominators(idom);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let mut mdb = X86MemDepBlock::new(mssa);
let def = mdb.find_nearest_dominating_def(2, 100, 8);
assert!(def.is_some());
}
#[test]
fn test_aa_query_cache_new_empty() {
let cache = X86AAQueryCache::new(10);
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_aa_query_cache_get_insert_hit() {
let mut cache = X86AAQueryCache::new(10);
cache.insert(1, 8, 2, 8, AliasResult::NoAlias);
let result = cache.get(1, 8, 2, 8);
assert_eq!(result, Some(AliasResult::NoAlias));
}
#[test]
fn test_aa_query_cache_symmetric_lookup() {
let mut cache = X86AAQueryCache::new(10);
cache.insert(1, 8, 2, 8, AliasResult::NoAlias);
let result = cache.get(2, 8, 1, 8);
assert_eq!(result, Some(AliasResult::NoAlias));
}
#[test]
fn test_aa_query_cache_eviction_lru() {
let mut cache = X86AAQueryCache::new(3);
cache.insert(1, 1, 1, 1, AliasResult::NoAlias);
cache.insert(2, 2, 2, 2, AliasResult::MayAlias);
cache.insert(3, 3, 3, 3, AliasResult::MustAlias);
let _ = cache.get(1, 1, 1, 1);
cache.insert(4, 4, 4, 4, AliasResult::PartialAlias);
assert_eq!(cache.len(), 3);
}
#[test]
fn test_aa_query_cache_clear() {
let mut cache = X86AAQueryCache::new(10);
cache.insert(1, 8, 2, 8, AliasResult::NoAlias);
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
}
#[test]
fn test_aa_query_cache_miss() {
let mut cache = X86AAQueryCache::new(10);
let result = cache.get(1, 8, 2, 8);
assert_eq!(result, None);
}
#[test]
fn test_aa_query_cache_zero_capacity() {
let mut cache = X86AAQueryCache::new(0);
cache.insert(1, 8, 2, 8, AliasResult::NoAlias);
assert!(cache.is_empty());
}
#[test]
fn test_pta_new_not_field_sensitive() {
let pta = X86PointsToAnalysis::new(false);
assert!(!pta.field_sensitive);
assert!(pta.stratified.is_none());
}
#[test]
fn test_pta_new_field_sensitive() {
let pta = X86PointsToAnalysis::new(true);
assert!(pta.field_sensitive);
assert!(pta.stratified.is_some());
}
#[test]
fn test_pta_may_alias_no_field() {
let mut pta = X86PointsToAnalysis::new(false);
pta.cfl.register_pointer(1);
pta.cfl.register_pointer(2);
pta.cfl.add_points_to(1, 100);
pta.cfl.add_points_to(2, 200);
pta.solve();
let result = pta.may_alias(1, 0, 8, 2, 0, 8);
assert_eq!(result, AliasResult::NoAlias);
}
#[test]
fn test_pta_field_sensitive_query_no_overlap() {
let mut pta = X86PointsToAnalysis::new(true);
if let Some(ref mut strat) = pta.stratified {
strat.add_field_points_to(1, 0, 100);
}
pta.solve();
let result = pta.may_alias(1, 0, 8, 1, 16, 8);
assert_eq!(result, AliasResult::NoAlias);
}
#[test]
fn test_pta_field_sensitive_query_must_alias() {
let mut pta = X86PointsToAnalysis::new(true);
if let Some(ref mut strat) = pta.stratified {
strat.add_field_points_to(1, 0, 100);
}
pta.solve();
let result = pta.may_alias(1, 0, 8, 1, 0, 8);
assert_eq!(result, AliasResult::MustAlias);
}
#[test]
fn test_pta_solve_twice_is_idempotent() {
let mut pta = X86PointsToAnalysis::new(false);
pta.solve();
let solved1 = pta.cfl.solved;
pta.solve();
let solved2 = pta.cfl.solved;
assert_eq!(solved1, solved2);
}
#[test]
fn test_full_pipeline_noalias_allocas_all_analyses() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_alloca(100, 8);
aa.basic_aa.register_alloca(200, 8);
aa.initialize();
let loc_a = MemoryLocation::new(100, 8);
let loc_b = MemoryLocation::new(200, 8);
let result = aa.alias(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::NoAlias);
assert_eq!(result.source, "BasicAA");
}
#[test]
fn test_full_pipeline_tbaa_noalias() {
let mut aa = X86AliasAnalysis::new();
aa.tbaa.add_type_node(TBAANode::new(1, 0, "root1"));
aa.tbaa.add_type_node(TBAANode::new(2, 1, "int"));
aa.tbaa.add_type_node(TBAANode::new(3, 0, "root2"));
aa.tbaa.add_type_node(TBAANode::new(4, 3, "float"));
aa.initialize();
let loc_a = MemoryLocation::new(100, 8).with_tbaa("int:2:1".to_string());
let loc_b = MemoryLocation::new(200, 8).with_tbaa("float:4:3".to_string());
let result = aa.alias(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::NoAlias);
}
#[test]
fn test_full_pipeline_scoped_noalias_works() {
let mut aa = X86AliasAnalysis::new();
aa.scoped_noalias_aa.register_noalias(1, 10, 20);
aa.initialize();
let loc_a = MemoryLocation::new(100, 8)
.with_scoped_noalias("1".to_string(), vec!["10".to_string()]);
let loc_b = MemoryLocation::new(200, 8)
.with_scoped_noalias("1".to_string(), vec!["20".to_string()]);
let result = aa.alias(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::NoAlias);
assert_eq!(result.source, "ScopedNoAliasAA");
}
#[test]
fn test_full_pipeline_get_mem_dep() {
let mut aa = X86AliasAnalysis::new();
aa.memory_ssa.set_entry_block(1);
aa.memory_ssa
.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
aa.initialize();
let result = aa.get_mem_dep(100, 8, 10);
assert!(matches!(
result.dep_kind,
X86MemDepKind::Def | X86MemDepKind::NonLocal | X86MemDepKind::Unknown
));
}
#[test]
fn test_full_pipeline_get_mod_ref_for_readnone() {
let mut aa = X86AliasAnalysis::new();
aa.globals_mod_ref
.register_function(100, FunctionModRefInfo::DoesNotAccessMemory);
aa.initialize();
let loc = MemoryLocation::new(500, 8);
let modref = aa.get_mod_ref_for_call(100, &loc);
assert_eq!(modref, ModRefInfo::NoModRef);
}
#[test]
fn test_full_pipeline_points_to_constant() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_global(500, 8, true);
aa.initialize();
let loc = MemoryLocation::new(500, 8);
assert!(aa.points_to_constant_memory(&loc));
}
#[test]
fn test_alias_reflexive_must_alias() {
let mut aa = X86AliasAnalysis::new();
aa.initialize();
let loc = MemoryLocation::new(42, 8);
let result = aa.alias(&loc, &loc);
assert_eq!(result.result, AliasResult::MustAlias);
}
#[test]
fn test_alias_symmetric_same_result() {
let mut aa = X86AliasAnalysis::new();
aa.basic_aa.register_alloca(100, 8);
aa.basic_aa.register_alloca(200, 8);
aa.initialize();
let loc_a = MemoryLocation::new(100, 8);
let loc_b = MemoryLocation::new(200, 8);
let r_ab = aa.alias(&loc_a, &loc_b).result;
let r_ba = aa.alias(&loc_b, &loc_a).result;
assert_eq!(r_ab, r_ba);
}
#[test]
fn test_alias_lattice_ordering() {
assert!(AliasResult::NoAlias <= AliasResult::MustAlias);
assert!(AliasResult::NoAlias <= AliasResult::MayAlias);
assert!(AliasResult::MayAlias <= AliasResult::MustAlias);
assert!(AliasResult::PartialAlias <= AliasResult::MustAlias);
}
#[test]
fn test_stratified_alias_same_pointer_same_offset_must_alias() {
let mut scfl = X86StratifiedCFLAA::new(4);
assert_eq!(
scfl.field_may_alias(100, 0, 8, 100, 0, 8),
AliasResult::MustAlias
);
}
#[test]
fn test_stress_basic_aa_many_geps() {
let mut basic = X86BasicAA::new();
for i in 0..200 {
basic.register_alloca(i * 100, 64);
basic.register_gep((i * 100) + 50, i * 100, 32);
}
let loc_a = MemoryLocation::new(50, 8);
let loc_b = MemoryLocation::new(150, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
assert!(basic.stats.gep_disambiguations > 0);
}
#[test]
fn test_stress_cfl_aa_long_chain() {
let mut cfl = X86CFLAA::new();
let count = 50;
for i in 1..=count {
cfl.register_pointer(i);
if i > 1 {
cfl.add_assign(i, i - 1);
}
}
cfl.add_points_to(1, 42);
cfl.solve();
assert!(cfl.get_points_to(count).objects.contains(&42));
}
#[test]
fn test_stress_alias_set_tracker_chain_merge() {
let mut tracker = X86AliasSetTracker::new();
for i in 1..=50 {
tracker.merge_sets(100 + i, 100 + i + 1);
}
let id_first = tracker.get_set_id(101);
let id_last = tracker.get_set_id(151);
assert_eq!(id_first, id_last);
assert_eq!(tracker.sets.len(), 1);
}
#[test]
fn test_stress_evaluator_100_queries() {
let mut eval = X86AliasEvaluator::new();
for i in 0..100 {
eval.add_ground_truth(i, 8, i + 100, 8, AliasResult::NoAlias);
}
for i in 0..100 {
eval.evaluate(
i,
8,
i + 100,
8,
X86AliasResult::new(AliasResult::NoAlias, "test"),
);
}
eval.compute_summary();
assert_eq!(eval.summary.total_queries, 100);
assert_eq!(eval.summary.incorrect, 0);
assert_eq!(eval.summary.soundness, 1.0);
assert_eq!(eval.summary.precision, 1.0);
}
#[test]
fn test_stress_memory_ssa_many_defs() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
let mut ops = Vec::new();
for i in 0..100 {
ops.push((1, i, X86Opcode::Store, 100, 8, false));
}
mssa.build(&ops, &[1]);
assert!(mssa.memory_defs.len() >= 100);
assert!(mssa.verify().is_ok());
}
#[test]
fn test_edge_zero_size_access() {
let loc_a = MemoryLocation::new(100, 0);
let loc_b = MemoryLocation::new(200, 0);
assert!(!loc_a.has_known_size);
assert!(!loc_b.has_known_size);
}
#[test]
fn test_edge_same_pointer_zero_and_nonzero_size() {
let mut basic = X86BasicAA::new();
let loc_a = MemoryLocation::new(100, 0);
let loc_b = MemoryLocation::new(100, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::MustAlias);
}
#[test]
fn test_edge_both_null_pointers() {
let mut basic = X86BasicAA::new();
let loc_a = MemoryLocation::new(0, 8);
let loc_b = MemoryLocation::new(0, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_edge_null_vs_non_null() {
let mut basic = X86BasicAA::new();
let loc_a = MemoryLocation::new(0, 8);
let loc_b = MemoryLocation::new(100, 8);
assert_eq!(basic.alias(&loc_a, &loc_b), AliasResult::NoAlias);
}
#[test]
fn test_edge_tbaa_missing_both_tags() {
let tbaa = X86TypeBasedAA::new();
assert_eq!(tbaa.may_alias(None, None), AliasResult::MayAlias);
}
#[test]
fn test_edge_scev_no_registered_values() {
let mut scev_aa = X86SCEVAA::new();
let loc_a = MemoryLocation::new(1, 8);
let loc_b = MemoryLocation::new(2, 8);
assert_eq!(scev_aa.alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_edge_empty_noalias_scopes() {
let loc = MemoryLocation::new(100, 8).with_scoped_noalias("1".to_string(), vec![]);
assert!(!loc.has_scoped_noalias());
}
#[test]
fn test_edge_memory_ssa_empty_function() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[], &[1]);
assert!(mssa.verify().is_ok());
assert_eq!(mssa.total_accesses(), 1); }
#[test]
fn test_edge_modref_meet_identity() {
let info = ModRefInfo::Ref;
assert_eq!(ModRefInfo::meet(info, info), info);
}
#[test]
fn test_edge_globals_modref_unknown_global_address_taken() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_global(42, true, false); assert!(gmr.function_modifies_global(999, 42));
}
#[test]
fn test_edge_globals_modref_non_address_taken_not_modified() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_global(42, false, false); assert!(!gmr.function_modifies_global(999, 42));
}
}
#[derive(Debug, Clone)]
pub struct X86AliasSetPrinter {
pub verbose: bool,
pub show_tbaa: bool,
pub show_scoped_noalias: bool,
pub show_points_to: bool,
}
impl X86AliasSetPrinter {
pub fn new() -> Self {
X86AliasSetPrinter {
verbose: false,
show_tbaa: true,
show_scoped_noalias: true,
show_points_to: false,
}
}
pub fn print_location(&self, loc: &MemoryLocation) -> String {
let mut s = format!("ptr=0x{:x}, size={}", loc.ptr, loc.size);
if self.show_tbaa && loc.has_tbaa() {
s.push_str(&format!(", tbaa={:?}", loc.tbaa_tag));
}
if self.show_scoped_noalias && loc.has_scoped_noalias() {
s.push_str(&format!(
", noalias_domain={:?}, scopes={:?}",
loc.noalias_domain, loc.noalias_scopes
));
}
s
}
pub fn print_points_to(&self, pts: &PointsToSet) -> String {
if pts.is_noalias {
return "noalias".to_string();
}
if pts.includes_unknown {
return "unknown".to_string();
}
let mut s = String::from("{");
let objs: Vec<String> = pts.objects.iter().map(|o| format!("0x{:x}", o)).collect();
s.push_str(&objs.join(", "));
if pts.includes_null {
s.push_str(", null");
}
s.push('}');
s
}
pub fn print_alias_set(&self, set: &AliasSet) -> String {
let mut s = format!("AliasSet[{}]: ", set.id);
s.push_str(&format!(
"pointers={}, access={:?}, volatile={}",
set.pointers.len(),
set.access_type,
set.is_volatile
));
if self.verbose {
s.push_str("\n Pointers: ");
let ptrs: Vec<String> = set.pointers.iter().map(|p| format!("0x{:x}", p)).collect();
s.push_str(&ptrs.join(", "));
}
s
}
pub fn print_alias_result(&self, result: &X86AliasResult) -> String {
let mut s = format!("AliasResult: {} (from {})", result.result, result.source);
if let Some(ref explanation) = result.explanation {
s.push_str(&format!("\n Explanation: {}", explanation));
}
s
}
pub fn print_mem_dep(&self, result: &X86MemDepResult) -> String {
format!(
"MemDep: kind={}, dep_inst={:?}, access_id={:?}",
result.dep_kind, result.dep_inst, result.access_id
)
}
pub fn print_stats_table(aa: &X86AliasAnalysis) -> String {
let mut s = String::new();
s.push_str("+---------------------------+-----------+\n");
s.push_str("| Analysis | Queries |\n");
s.push_str("+---------------------------+-----------+\n");
s.push_str(&format!(
"| BasicAA | {:>9} |\n",
aa.basic_aa.stats.total_queries
));
s.push_str(&format!(
"| CFL-AA | {:>9} |\n",
aa.cfl_aa.stats.queries_resolved
));
s.push_str(&format!(
"| SCEV-AA | {:>9} |\n",
aa.scev_aa.stats.total_queries
));
s.push_str(&format!(
"| GlobalsModRef | {:>9} |\n",
aa.globals_mod_ref.stats.queries_resolved
));
s.push_str(&format!(
"| TypeBasedAA | {:>9} |\n",
aa.tbaa.stats.total_queries
));
s.push_str(&format!(
"| ScopedNoAliasAA | {:>9} |\n",
aa.scoped_noalias_aa.stats.total_queries
));
s.push_str("+---------------------------+-----------+\n");
s.push_str(&format!(
"| Total | {:>9} |\n",
aa.total_queries
));
s.push_str("+---------------------------+-----------+\n");
s
}
}
impl Default for X86AliasSetPrinter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86AAConfig {
pub enable_basic_aa: bool,
pub enable_cfl_aa: bool,
pub enable_scev_aa: bool,
pub enable_globals_mod_ref: bool,
pub enable_tbaa: bool,
pub enable_scoped_noalias: bool,
pub enable_memory_ssa: bool,
pub enable_mem_dep: bool,
pub max_recursion_depth: usize,
pub alias_cache_size: usize,
pub memdep_cache_size: usize,
pub optimize_memory_ssa: bool,
pub field_sensitive_cfl: bool,
pub cfl_max_iterations: u64,
pub scev_max_backedge_count: u64,
}
impl Default for X86AAConfig {
fn default() -> Self {
X86AAConfig {
enable_basic_aa: true,
enable_cfl_aa: true,
enable_scev_aa: true,
enable_globals_mod_ref: true,
enable_tbaa: true,
enable_scoped_noalias: true,
enable_memory_ssa: true,
enable_mem_dep: true,
max_recursion_depth: X86_ALIAS_MAX_RECURSION_DEPTH,
alias_cache_size: 2048,
memdep_cache_size: X86_MEMDEP_CACHE_SIZE,
optimize_memory_ssa: true,
field_sensitive_cfl: false,
cfl_max_iterations: 1_000_000,
scev_max_backedge_count: X86_SCEV_MAX_BACKEDGE_TAKEN_COUNT,
}
}
}
impl X86AAConfig {
pub fn fast() -> Self {
X86AAConfig {
enable_basic_aa: true,
enable_cfl_aa: false,
enable_scev_aa: false,
enable_globals_mod_ref: true,
enable_tbaa: true,
enable_scoped_noalias: false,
enable_memory_ssa: false,
enable_mem_dep: false,
..Default::default()
}
}
pub fn precise() -> Self {
X86AAConfig {
field_sensitive_cfl: true,
..Default::default()
}
}
pub fn minimal() -> Self {
X86AAConfig {
enable_basic_aa: true,
enable_cfl_aa: false,
enable_scev_aa: false,
enable_globals_mod_ref: false,
enable_tbaa: false,
enable_scoped_noalias: false,
enable_memory_ssa: false,
enable_mem_dep: false,
optimize_memory_ssa: false,
..Default::default()
}
}
pub fn build_analysis(&self) -> X86AliasAnalysis {
let mut aa = X86AliasAnalysis::new();
if !self.enable_cfl_aa {
aa.cfl_aa = X86CFLAA::new();
}
if !self.enable_scev_aa {
aa.scev_aa = X86SCEVAA::new();
}
aa.max_alias_cache_size = self.alias_cache_size;
if self.enable_memory_ssa && self.optimize_memory_ssa {
aa.optimize_memory_ssa();
}
if self.enable_mem_dep {
aa.initialize();
}
aa
}
}
pub fn is_constant_tbaa_access(tag: Option<&TBAATag>, tbaa: &X86TypeBasedAA) -> bool {
tbaa.is_constant_tag(tag)
}
pub fn merge_noalias_domains(
domain_a: Option<&str>,
scopes_a: &[String],
domain_b: Option<&str>,
scopes_b: &[String],
) -> (Option<String>, Vec<String>) {
let domain = match (domain_a, domain_b) {
(Some(a), Some(b)) if a == b => Some(a.to_string()),
(Some(a), None) => Some(a.to_string()),
(None, Some(b)) => Some(b.to_string()),
_ => None,
};
let mut scopes = Vec::new();
scopes.extend(scopes_a.iter().cloned());
for s in scopes_b {
if !scopes.contains(s) {
scopes.push(s.clone());
}
}
(domain, scopes)
}
pub fn check_alias_consistency(r1: AliasResult, r2: AliasResult) -> bool {
match (r1, r2) {
(AliasResult::NoAlias, AliasResult::NoAlias) => true,
(AliasResult::MustAlias, AliasResult::MustAlias) => true,
(AliasResult::NoAlias, AliasResult::MustAlias) => false,
(AliasResult::MustAlias, AliasResult::NoAlias) => false,
_ => true, }
}
pub fn alias_result_distance(a: AliasResult, b: AliasResult) -> f64 {
if a == b {
return 0.0;
}
match (a, b) {
(AliasResult::NoAlias, AliasResult::MustAlias) => 1.0,
(AliasResult::MustAlias, AliasResult::NoAlias) => 1.0,
(AliasResult::NoAlias, AliasResult::MayAlias) => 0.5,
(AliasResult::MayAlias, AliasResult::NoAlias) => 0.5,
(AliasResult::MustAlias, AliasResult::MayAlias) => 0.5,
(AliasResult::MayAlias, AliasResult::MustAlias) => 0.5,
_ => 0.75,
}
}
pub fn sort_pointers(pointers: &HashSet<u64>) -> Vec<u64> {
let mut v: Vec<u64> = pointers.iter().copied().collect();
v.sort();
v
}
pub fn intersect_pointers(a: &HashSet<u64>, b: &HashSet<u64>) -> HashSet<u64> {
a.intersection(b).copied().collect()
}
pub fn union_pointers(a: &HashSet<u64>, b: &HashSet<u64>) -> HashSet<u64> {
a.union(b).copied().collect()
}
#[derive(Debug, Clone)]
pub struct X86AliasAnalysisPipeline {
pub config: X86AAConfig,
pub analysis: Option<X86AliasAnalysis>,
pub stage_log: Vec<String>,
}
impl X86AliasAnalysisPipeline {
pub fn new() -> Self {
X86AliasAnalysisPipeline {
config: X86AAConfig::default(),
analysis: None,
stage_log: Vec::new(),
}
}
pub fn with_config(config: X86AAConfig) -> Self {
X86AliasAnalysisPipeline {
config,
analysis: None,
stage_log: Vec::new(),
}
}
pub fn build(&mut self) {
self.analysis = Some(self.config.build_analysis());
self.stage_log
.push("Built analysis from config".to_string());
}
pub fn register_alloca(&mut self, ptr: u64, size: u64) {
if let Some(ref mut aa) = self.analysis {
aa.basic_aa.register_alloca(ptr, size);
self.stage_log
.push(format!("Registered alloca: ptr=0x{:x}, size={}", ptr, size));
}
}
pub fn register_global(&mut self, ptr: u64, size: u64, is_constant: bool) {
if let Some(ref mut aa) = self.analysis {
aa.basic_aa.register_global(ptr, size, is_constant);
if self.config.enable_globals_mod_ref {
aa.globals_mod_ref.register_global(ptr, true, is_constant);
}
self.stage_log
.push(format!("Registered global: ptr=0x{:x}, size={}", ptr, size));
}
}
pub fn register_gep(&mut self, gep_ptr: u64, base: u64, offset: i64) {
if let Some(ref mut aa) = self.analysis {
aa.basic_aa.register_gep(gep_ptr, base, offset);
self.stage_log.push(format!(
"Registered GEP: gep=0x{:x}, base=0x{:x}, offset={}",
gep_ptr, base, offset
));
}
}
pub fn register_tbaa_node(&mut self, node: TBAANode) {
if let Some(ref mut aa) = self.analysis {
aa.tbaa.add_type_node(node);
self.stage_log.push("Registered TBAA node".to_string());
}
}
pub fn register_noalias_scope(&mut self, domain: u64, scope_a: u64, scope_b: u64) {
if let Some(ref mut aa) = self.analysis {
aa.scoped_noalias_aa
.register_noalias(domain, scope_a, scope_b);
self.stage_log.push(format!(
"Registered scoped noalias: domain={}, {} <-> {}",
domain, scope_a, scope_b
));
}
}
pub fn register_cfl_pointer(&mut self, ptr: u64) {
if let Some(ref mut aa) = self.analysis {
aa.cfl_aa.register_pointer(ptr);
self.stage_log
.push(format!("Registered CFL pointer: 0x{:x}", ptr));
}
}
pub fn add_cfl_assign(&mut self, lhs: u64, rhs: u64) {
if let Some(ref mut aa) = self.analysis {
aa.cfl_aa.add_assign(lhs, rhs);
}
}
pub fn register_function_behavior(&mut self, func_id: u64, behavior: FunctionModRefInfo) {
if let Some(ref mut aa) = self.analysis {
aa.globals_mod_ref.register_function(func_id, behavior);
}
}
pub fn build_memory_ssa(
&mut self,
ops: &[(u64, u64, X86Opcode, u64, u64, bool)],
block_order: &[u64],
) {
if let Some(ref mut aa) = self.analysis {
aa.build_memory_ssa(ops, block_order);
self.stage_log.push("Built MemorySSA".to_string());
}
}
pub fn initialize(&mut self) {
if let Some(ref mut aa) = self.analysis {
aa.initialize();
self.stage_log.push("Initialized all analyses".to_string());
}
}
pub fn query(&mut self, loc_a: &MemoryLocation, loc_b: &MemoryLocation) -> X86AliasResult {
match self.analysis {
Some(ref mut aa) => aa.alias(loc_a, loc_b),
None => X86AliasResult::new(AliasResult::MayAlias, "uninitialized"),
}
}
pub fn get_log(&self) -> &[String] {
&self.stage_log
}
pub fn print_pipeline_summary(&self) -> String {
let mut s = String::new();
s.push_str("=== X86 Alias Analysis Pipeline Summary ===\n");
s.push_str(&format!("Configuration:\n"));
s.push_str(&format!(
" BasicAA: {}\n",
self.config.enable_basic_aa
));
s.push_str(&format!(
" CFL-AA: {}\n",
self.config.enable_cfl_aa
));
s.push_str(&format!(
" SCEV-AA: {}\n",
self.config.enable_scev_aa
));
s.push_str(&format!(
" GlobalsModRef: {}\n",
self.config.enable_globals_mod_ref
));
s.push_str(&format!(" TypeBasedAA: {}\n", self.config.enable_tbaa));
s.push_str(&format!(
" ScopedNoAlias: {}\n",
self.config.enable_scoped_noalias
));
s.push_str(&format!(
" MemorySSA: {}\n",
self.config.enable_memory_ssa
));
s.push_str(&format!(
" MemDep: {}\n",
self.config.enable_mem_dep
));
s.push_str(&format!("\nStages executed: {}\n", self.stage_log.len()));
for (i, stage) in self.stage_log.iter().enumerate() {
s.push_str(&format!(" {}. {}\n", i + 1, stage));
}
if let Some(ref aa) = self.analysis {
s.push_str("\n");
s.push_str(&X86AliasSetPrinter::print_stats_table(aa));
}
s
}
}
impl Default for X86AliasAnalysisPipeline {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod pipeline_tests {
use super::*;
#[test]
fn test_pipeline_new() {
let pipeline = X86AliasAnalysisPipeline::new();
assert!(pipeline.analysis.is_none());
assert!(pipeline.stage_log.is_empty());
}
#[test]
fn test_pipeline_build() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
assert!(pipeline.analysis.is_some());
assert!(!pipeline.stage_log.is_empty());
}
#[test]
fn test_pipeline_register_alloca() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.register_alloca(100, 8);
assert_eq!(pipeline.stage_log.len(), 2);
}
#[test]
fn test_pipeline_query() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.register_alloca(100, 8);
pipeline.register_alloca(200, 8);
pipeline.initialize();
let loc_a = MemoryLocation::new(100, 8);
let loc_b = MemoryLocation::new(200, 8);
let result = pipeline.query(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::NoAlias);
}
#[test]
fn test_pipeline_uninitialized_query() {
let mut pipeline = X86AliasAnalysisPipeline::new();
let loc_a = MemoryLocation::new(100, 8);
let loc_b = MemoryLocation::new(200, 8);
let result = pipeline.query(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::MayAlias);
assert_eq!(result.source, "uninitialized");
}
#[test]
fn test_pipeline_print_summary() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
let summary = pipeline.print_pipeline_summary();
assert!(summary.contains("X86 Alias Analysis Pipeline Summary"));
assert!(summary.contains("BasicAA"));
}
#[test]
fn test_pipeline_with_config_fast() {
let config = X86AAConfig::fast();
let pipeline = X86AliasAnalysisPipeline::with_config(config);
assert!(!pipeline.config.enable_cfl_aa);
assert!(!pipeline.config.enable_scev_aa);
}
#[test]
fn test_pipeline_with_config_precise() {
let config = X86AAConfig::precise();
let pipeline = X86AliasAnalysisPipeline::with_config(config);
assert!(pipeline.config.field_sensitive_cfl);
}
#[test]
fn test_pipeline_with_config_minimal() {
let config = X86AAConfig::minimal();
let pipeline = X86AliasAnalysisPipeline::with_config(config);
assert!(pipeline.config.enable_basic_aa);
assert!(!pipeline.config.enable_cfl_aa);
assert!(!pipeline.config.enable_memory_ssa);
}
#[test]
fn test_pipeline_register_gep() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.register_alloca(1, 64);
pipeline.register_gep(10, 1, 16);
assert_eq!(pipeline.stage_log.len(), 3);
}
#[test]
fn test_pipeline_register_tbaa_node() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.register_tbaa_node(TBAANode::new(1, 0, "int"));
assert_eq!(pipeline.stage_log.len(), 2);
}
#[test]
fn test_pipeline_register_noalias_scope() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.register_noalias_scope(1, 10, 20);
assert_eq!(pipeline.stage_log.len(), 2);
}
#[test]
fn test_pipeline_register_cfl_pointer() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.register_cfl_pointer(1);
assert_eq!(pipeline.stage_log.len(), 2);
}
#[test]
fn test_pipeline_register_function_behavior() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.register_function_behavior(100, FunctionModRefInfo::DoesNotAccessMemory);
assert_eq!(pipeline.stage_log.len(), 2);
}
#[test]
fn test_pipeline_build_memory_ssa() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
pipeline.build_memory_ssa(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
assert_eq!(pipeline.stage_log.len(), 2);
}
#[test]
fn test_printer_new() {
let printer = X86AliasSetPrinter::new();
assert!(printer.show_tbaa);
assert!(!printer.verbose);
}
#[test]
fn test_printer_print_location() {
let printer = X86AliasSetPrinter::new();
let loc = MemoryLocation::new(0x100, 8).with_tbaa("int".to_string());
let s = printer.print_location(&loc);
assert!(s.contains("0x100"));
assert!(s.contains("int"));
}
#[test]
fn test_printer_print_points_to() {
let printer = X86AliasSetPrinter::new();
let pts = PointsToSet::singleton(0x42);
let s = printer.print_points_to(&pts);
assert!(s.contains("0x42"));
}
#[test]
fn test_printer_print_alias_set() {
let printer = X86AliasSetPrinter::new();
let mut set = AliasSet::new(1);
set.add_pointer(0x10);
let s = printer.print_alias_set(&set);
assert!(s.contains("AliasSet[1]"));
}
#[test]
fn test_printer_print_alias_result() {
let printer = X86AliasSetPrinter::new();
let result = X86AliasResult::new(AliasResult::NoAlias, "BasicAA")
.with_explanation("Distinct allocas");
let s = printer.print_alias_result(&result);
assert!(s.contains("NoAlias"));
assert!(s.contains("BasicAA"));
assert!(s.contains("Distinct allocas"));
}
#[test]
fn test_printer_print_mem_dep() {
let printer = X86AliasSetPrinter::new();
let result = X86MemDepResult {
dep_inst: Some(42),
dep_kind: X86MemDepKind::Def,
access_id: Some(10),
};
let s = printer.print_mem_dep(&result);
assert!(s.contains("Def"));
assert!(s.contains("42"));
}
#[test]
fn test_printer_stats_table() {
let aa = X86AliasAnalysis::new();
let table = X86AliasSetPrinter::print_stats_table(&aa);
assert!(table.contains("BasicAA"));
assert!(table.contains("Total"));
}
#[test]
fn test_aa_config_default() {
let config = X86AAConfig::default();
assert!(config.enable_basic_aa);
assert!(config.enable_cfl_aa);
assert!(config.enable_scev_aa);
}
#[test]
fn test_aa_config_build_analysis() {
let config = X86AAConfig::default();
let aa = config.build_analysis();
assert!(aa.max_alias_cache_size > 0);
}
#[test]
fn test_merge_noalias_domains_same() {
let (domain, scopes) = merge_noalias_domains(
Some("1"),
&["10".to_string()],
Some("1"),
&["20".to_string()],
);
assert_eq!(domain, Some("1".to_string()));
assert_eq!(scopes.len(), 2);
}
#[test]
fn test_merge_noalias_domains_different() {
let (domain, scopes) = merge_noalias_domains(
Some("1"),
&["10".to_string()],
Some("2"),
&["20".to_string()],
);
assert_eq!(domain, None);
assert_eq!(scopes.len(), 2);
}
#[test]
fn test_check_alias_consistency_same() {
assert!(check_alias_consistency(
AliasResult::NoAlias,
AliasResult::NoAlias
));
assert!(check_alias_consistency(
AliasResult::MustAlias,
AliasResult::MustAlias
));
}
#[test]
fn test_check_alias_consistency_conflict() {
assert!(!check_alias_consistency(
AliasResult::NoAlias,
AliasResult::MustAlias
));
}
#[test]
fn test_check_alias_consistency_mayalias() {
assert!(check_alias_consistency(
AliasResult::MayAlias,
AliasResult::NoAlias
));
assert!(check_alias_consistency(
AliasResult::MayAlias,
AliasResult::MustAlias
));
}
#[test]
fn test_alias_result_distance_identical() {
assert_eq!(
alias_result_distance(AliasResult::NoAlias, AliasResult::NoAlias),
0.0
);
}
#[test]
fn test_alias_result_distance_opposite() {
assert_eq!(
alias_result_distance(AliasResult::NoAlias, AliasResult::MustAlias),
1.0
);
}
#[test]
fn test_sort_pointers() {
let mut set = HashSet::new();
set.insert(30);
set.insert(10);
set.insert(20);
let sorted = sort_pointers(&set);
assert_eq!(sorted, vec![10, 20, 30]);
}
#[test]
fn test_intersect_pointers() {
let mut a = HashSet::new();
a.insert(1);
a.insert(2);
a.insert(3);
let mut b = HashSet::new();
b.insert(2);
b.insert(3);
b.insert(4);
let inter = intersect_pointers(&a, &b);
assert_eq!(inter.len(), 2);
assert!(inter.contains(&2));
assert!(inter.contains(&3));
}
#[test]
fn test_union_pointers() {
let mut a = HashSet::new();
a.insert(1);
a.insert(2);
let mut b = HashSet::new();
b.insert(2);
b.insert(3);
let union = union_pointers(&a, &b);
assert_eq!(union.len(), 3);
assert!(union.contains(&1));
assert!(union.contains(&2));
assert!(union.contains(&3));
}
}
#[derive(Debug, Clone)]
pub struct X86MemDepEvaluator {
pub ground_truth: HashMap<(u64, u64, u64), Option<u64>>,
pub results: Vec<X86MemDepEvalRecord>,
pub summary: X86MemDepEvalSummary,
}
#[derive(Debug, Clone)]
pub struct X86MemDepEvalRecord {
pub ptr: u64,
pub size: u64,
pub from_inst: u64,
pub expected_dep: Option<u64>,
pub actual_dep: Option<u64>,
pub correct: bool,
}
#[derive(Debug, Clone, Default)]
pub struct X86MemDepEvalSummary {
pub total_queries: u64,
pub exact_matches: u64,
pub conservative_matches: u64,
pub incorrect: u64,
pub accuracy: f64,
}
impl X86MemDepEvaluator {
pub fn new() -> Self {
X86MemDepEvaluator {
ground_truth: HashMap::new(),
results: Vec::new(),
summary: X86MemDepEvalSummary::default(),
}
}
pub fn add_ground_truth(&mut self, ptr: u64, size: u64, from_inst: u64, dep_inst: Option<u64>) {
self.ground_truth.insert((ptr, size, from_inst), dep_inst);
}
pub fn evaluate(&mut self, ptr: u64, size: u64, from_inst: u64, result: &X86MemDepResult) {
let expected = self
.ground_truth
.get(&(ptr, size, from_inst))
.copied()
.unwrap_or(None);
let correct = match (expected, result.dep_inst) {
(Some(exp), Some(act)) => exp == act || act < exp, (None, None) => true,
(None, Some(_)) => true, (Some(_), None) => false, };
self.results.push(X86MemDepEvalRecord {
ptr,
size,
from_inst,
expected_dep: expected,
actual_dep: result.dep_inst,
correct,
});
}
pub fn compute_summary(&mut self) {
let mut summary = X86MemDepEvalSummary::default();
summary.total_queries = self.results.len() as u64;
for record in &self.results {
if record.correct {
if record.expected_dep == record.actual_dep {
summary.exact_matches += 1;
} else {
summary.conservative_matches += 1;
}
} else {
summary.incorrect += 1;
}
}
summary.accuracy = if summary.total_queries > 0 {
(summary.exact_matches + summary.conservative_matches) as f64
/ summary.total_queries as f64
} else {
1.0
};
self.summary = summary;
}
pub fn report(&self) -> String {
let s = &self.summary;
format!(
"MemDep Evaluation Report\n\
=======================\n\
Total queries: {}\n\
Exact matches: {}\n\
Conservative matches:{}\n\
Incorrect: {}\n\
Accuracy: {:.2}%\n",
s.total_queries,
s.exact_matches,
s.conservative_matches,
s.incorrect,
s.accuracy * 100.0,
)
}
}
impl Default for X86MemDepEvaluator {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct X86AliasAnalysisDriver {
pub aa: X86AliasAnalysis,
pub printer: X86AliasSetPrinter,
pub evaluator: X86AliasEvaluator,
}
impl X86AliasAnalysisDriver {
pub fn new() -> Self {
X86AliasAnalysisDriver {
aa: X86AliasAnalysis::new(),
printer: X86AliasSetPrinter::new(),
evaluator: X86AliasEvaluator::new(),
}
}
pub fn init(&mut self) {
self.aa.initialize();
}
pub fn register_and_query_allocas(
&mut self,
ptr_a: u64,
size_a: u64,
ptr_b: u64,
size_b: u64,
) -> X86AliasResult {
self.aa.basic_aa.register_alloca(ptr_a, size_a);
self.aa.basic_aa.register_alloca(ptr_b, size_b);
let loc_a = MemoryLocation::new(ptr_a, size_a);
let loc_b = MemoryLocation::new(ptr_b, size_b);
let result = self.aa.alias(&loc_a, &loc_b);
if ptr_a != ptr_b {
self.evaluator
.add_ground_truth(ptr_a, size_a, ptr_b, size_b, AliasResult::NoAlias);
} else {
self.evaluator
.add_ground_truth(ptr_a, size_a, ptr_b, size_b, AliasResult::MustAlias);
}
self.evaluator
.evaluate(ptr_a, size_a, ptr_b, size_b, result.clone());
result
}
pub fn run_batch_and_summarize(&mut self, queries: &[(u64, u64, u64, u64)]) -> String {
let mut results = Vec::new();
for &(ptr_a, size_a, ptr_b, size_b) in queries {
let loc_a = MemoryLocation::new(ptr_a, size_a);
let loc_b = MemoryLocation::new(ptr_b, size_b);
results.push(self.aa.alias(&loc_a, &loc_b));
}
let mut output = String::new();
output.push_str("Batch Query Results:\n");
output.push_str("====================\n");
let noalias_count = results.iter().filter(|r| r.result.is_no_alias()).count();
let mustalias_count = results.iter().filter(|r| r.result.is_must_alias()).count();
let total = results.len();
output.push_str(&format!("Total queries: {}\n", total));
output.push_str(&format!("NoAlias: {}\n", noalias_count));
output.push_str(&format!("MustAlias: {}\n", mustalias_count));
output.push_str(&format!(
"Other: {}\n",
total - noalias_count - mustalias_count
));
output
}
pub fn full_report(&mut self) -> String {
self.evaluator.compute_summary();
let mut report = String::new();
report.push_str("========================================\n");
report.push_str(" X86 ALIAS ANALYSIS FULL REPORT\n");
report.push_str("========================================\n\n");
report.push_str(&self.aa.print_stats());
report.push('\n');
report.push_str(&self.evaluator.report());
report.push('\n');
report.push_str(&X86AliasSetPrinter::print_stats_table(&self.aa));
report
}
}
impl Default for X86AliasAnalysisDriver {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod final_tests {
use super::*;
#[test]
fn test_mem_dep_evaluator_new() {
let eval = X86MemDepEvaluator::new();
assert!(eval.ground_truth.is_empty());
}
#[test]
fn test_mem_dep_evaluator_exact_match() {
let mut eval = X86MemDepEvaluator::new();
eval.add_ground_truth(100, 8, 10, Some(5));
let result = X86MemDepResult {
dep_inst: Some(5),
dep_kind: X86MemDepKind::Def,
access_id: Some(1),
};
eval.evaluate(100, 8, 10, &result);
assert!(eval.results[0].correct);
}
#[test]
fn test_mem_dep_evaluator_missed_dep() {
let mut eval = X86MemDepEvaluator::new();
eval.add_ground_truth(100, 8, 10, Some(5));
let result = X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::NonLocal,
access_id: None,
};
eval.evaluate(100, 8, 10, &result);
assert!(!eval.results[0].correct);
}
#[test]
fn test_mem_dep_evaluator_conservative() {
let mut eval = X86MemDepEvaluator::new();
eval.add_ground_truth(100, 8, 10, Some(5));
let result = X86MemDepResult {
dep_inst: Some(3), dep_kind: X86MemDepKind::Def,
access_id: Some(1),
};
eval.evaluate(100, 8, 10, &result);
assert!(eval.results[0].correct);
}
#[test]
fn test_mem_dep_evaluator_summary() {
let mut eval = X86MemDepEvaluator::new();
eval.add_ground_truth(100, 8, 10, Some(5));
eval.add_ground_truth(200, 4, 20, None);
eval.evaluate(
100,
8,
10,
&X86MemDepResult {
dep_inst: Some(5),
dep_kind: X86MemDepKind::Def,
access_id: Some(1),
},
);
eval.evaluate(
200,
4,
20,
&X86MemDepResult {
dep_inst: None,
dep_kind: X86MemDepKind::NonLocal,
access_id: None,
},
);
eval.compute_summary();
assert_eq!(eval.summary.total_queries, 2);
assert_eq!(eval.summary.exact_matches, 2);
assert_eq!(eval.summary.incorrect, 0);
assert_eq!(eval.summary.accuracy, 1.0);
}
#[test]
fn test_mem_dep_evaluator_report() {
let eval = X86MemDepEvaluator::new();
let report = eval.report();
assert!(report.contains("MemDep Evaluation Report"));
assert!(report.contains("Accuracy"));
}
#[test]
fn test_driver_new() {
let driver = X86AliasAnalysisDriver::new();
assert!(!driver.aa.initialized);
}
#[test]
fn test_driver_init() {
let mut driver = X86AliasAnalysisDriver::new();
driver.init();
assert!(driver.aa.initialized);
}
#[test]
fn test_driver_register_and_query_allocas_different() {
let mut driver = X86AliasAnalysisDriver::new();
driver.init();
let result = driver.register_and_query_allocas(100, 8, 200, 8);
assert_eq!(result.result, AliasResult::NoAlias);
}
#[test]
fn test_driver_register_and_query_allocas_same() {
let mut driver = X86AliasAnalysisDriver::new();
driver.init();
let result = driver.register_and_query_allocas(100, 8, 100, 8);
assert_eq!(result.result, AliasResult::MustAlias);
}
#[test]
fn test_driver_run_batch_and_summarize() {
let mut driver = X86AliasAnalysisDriver::new();
driver.init();
driver.aa.basic_aa.register_alloca(100, 8);
driver.aa.basic_aa.register_alloca(200, 8);
driver.aa.basic_aa.register_alloca(300, 8);
let queries = vec![(100, 8, 200, 8), (100, 8, 300, 8), (200, 8, 300, 8)];
let summary = driver.run_batch_and_summarize(&queries);
assert!(summary.contains("Batch Query Results"));
assert!(summary.contains("NoAlias: 3"));
}
#[test]
fn test_driver_full_report() {
let mut driver = X86AliasAnalysisDriver::new();
driver.init();
driver.aa.basic_aa.register_alloca(100, 8);
driver.aa.basic_aa.register_alloca(200, 8);
let _ = driver.register_and_query_allocas(100, 8, 200, 8);
let _ = driver.register_and_query_allocas(100, 8, 100, 8);
let report = driver.full_report();
assert!(report.contains("X86 ALIAS ANALYSIS FULL REPORT"));
assert!(report.contains("BasicAA"));
}
#[test]
fn test_is_constant_tbaa_access() {
let mut tbaa = X86TypeBasedAA::new();
tbaa.add_type_node(TBAANode::new_constant(1, 0, "const_int"));
let tag = TBAATag::new_constant(1, 0, 0);
assert!(is_constant_tbaa_access(Some(&tag), &tbaa));
assert!(!is_constant_tbaa_access(None, &tbaa));
}
#[test]
fn test_alias_result_distance_partial() {
let d = alias_result_distance(AliasResult::NoAlias, AliasResult::MayAlias);
assert_eq!(d, 0.5);
let d = alias_result_distance(AliasResult::MayAlias, AliasResult::MustAlias);
assert_eq!(d, 0.5);
}
#[test]
fn test_check_alias_consistency_partialalias() {
assert!(check_alias_consistency(
AliasResult::PartialAlias,
AliasResult::MayAlias
));
assert!(check_alias_consistency(
AliasResult::PartialAlias,
AliasResult::MustAlias
));
}
#[test]
fn test_comprehensive_workflow() {
let mut pipeline = X86AliasAnalysisPipeline::new();
pipeline.build();
for i in 0..10 {
pipeline.register_alloca(i * 10, 8);
}
pipeline.register_global(1000, 4, false);
pipeline.register_global(2000, 4, true);
pipeline.register_gep(500, 0, 0);
pipeline.register_gep(510, 0, 8);
pipeline.register_tbaa_node(TBAANode::new(1, 0, "root"));
pipeline.register_tbaa_node(TBAANode::new(2, 1, "int"));
pipeline.register_tbaa_node(TBAANode::new(3, 1, "float"));
pipeline.register_noalias_scope(1, 10, 20);
pipeline.register_function_behavior(100, FunctionModRefInfo::OnlyReadsMemory);
pipeline.build_memory_ssa(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
pipeline.initialize();
let loc_a = MemoryLocation::new(0, 8);
let loc_b = MemoryLocation::new(10, 8);
let result = pipeline.query(&loc_a, &loc_b);
assert_eq!(result.result, AliasResult::NoAlias);
let result2 = pipeline.query(&loc_a, &loc_a);
assert_eq!(result2.result, AliasResult::MustAlias);
let summary = pipeline.print_pipeline_summary();
assert!(summary.contains("X86 Alias Analysis Pipeline Summary"));
assert!(summary.contains("Stages executed"));
}
#[test]
fn test_comprehensive_with_driver() {
let mut driver = X86AliasAnalysisDriver::new();
driver.init();
for i in 0..5 {
driver.aa.basic_aa.register_alloca(i * 100, 16);
}
for i in 0..5 {
for j in (i + 1)..5 {
let result = driver.register_and_query_allocas(i * 100, 16, j * 100, 16);
assert_eq!(result.result, AliasResult::NoAlias);
}
}
let report = driver.full_report();
assert!(report.contains("FULL REPORT"));
assert!(report.contains("Evaluation Report"));
}
#[test]
fn test_aa_config_fast_builds_correctly() {
let config = X86AAConfig::fast();
let aa = config.build_analysis();
assert!(aa.basic_aa.alloca_sizes.is_empty());
assert!(!aa.cfl_aa.solved);
}
#[test]
fn test_aa_config_precise_features() {
let config = X86AAConfig::precise();
assert!(config.field_sensitive_cfl);
assert!(config.enable_cfl_aa);
assert!(config.enable_scev_aa);
}
#[test]
fn test_aa_config_minimal_only_basic() {
let config = X86AAConfig::minimal();
assert!(config.enable_basic_aa);
assert!(!config.enable_cfl_aa);
assert!(!config.enable_tbaa);
assert!(!config.enable_memory_ssa);
}
}
#[cfg(test)]
mod final_coverage_tests {
use super::*;
#[test]
fn test_x86_opcode_display_all() {
let ops = vec![
X86Opcode::Store,
X86Opcode::Load,
X86Opcode::Alloca,
X86Opcode::Call,
X86Opcode::Add,
];
for op in ops {
assert!(!format!("{}", op).is_empty());
}
}
#[test]
fn test_memory_location_eq() {
let loc1 = MemoryLocation::new(100, 8);
let loc2 = MemoryLocation::new(100, 8);
let loc3 = MemoryLocation::new(200, 8);
assert_eq!(loc1, loc2);
assert_ne!(loc1, loc3);
}
#[test]
fn test_alias_result_meet_mustalias_noalias() {
assert_eq!(
AliasResult::meet(AliasResult::MustAlias, AliasResult::NoAlias),
AliasResult::NoAlias
);
}
#[test]
fn test_basic_aa_stats_default_zero() {
let basic = X86BasicAA::new();
let stats = basic.get_stats();
assert_eq!(stats.total_queries, 0);
assert_eq!(stats.noalias_results, 0);
}
#[test]
fn test_cfl_aa_empty_solve() {
let mut cfl = X86CFLAA::new();
cfl.solve();
assert!(cfl.solved);
assert_eq!(cfl.may_alias(1, 2), AliasResult::NoAlias);
}
#[test]
fn test_memory_ssa_get_location_def() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let loc = mssa.get_location(2); assert!(loc.is_some());
assert_eq!(loc.unwrap().ptr, 100);
}
#[test]
fn test_memory_ssa_is_same_location() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(
&[
(1, 10, X86Opcode::Store, 100, 8, false),
(1, 11, X86Opcode::Load, 100, 8, true),
],
&[1],
);
let acc1 = mssa.get_memory_access(10).unwrap();
let acc2 = mssa.get_memory_access(11).unwrap();
assert!(mssa.is_same_memory_location(acc1, acc2));
}
#[test]
fn test_mssa_updater_move_updates_block() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let mut updater = X86MemorySSAUpdater::new(mssa);
updater.move_instruction(10, 1, 3);
assert!(updater.update_log.len() > 0);
assert!(!updater.mssa.is_optimized);
}
#[test]
fn test_mem_dep_invalidate_inst() {
let mut mssa = X86MemorySSA::new();
mssa.set_entry_block(1);
mssa.build(&[(1, 10, X86Opcode::Store, 100, 8, false)], &[1]);
let mut mem_dep = X86MemDep::new(mssa);
let _ = mem_dep.get_dependency(100, 8, 10);
let _ = mem_dep.get_dependency(200, 4, 20);
assert_eq!(mem_dep.cache_size(), 2);
mem_dep.invalidate_inst(10);
assert_eq!(mem_dep.cache_size(), 1);
}
#[test]
fn test_scoped_noalias_missing_one_side() {
let aa = X86ScopedNoAliasAA::new();
let loc_a = MemoryLocation::new(100, 8)
.with_scoped_noalias("1".to_string(), vec!["10".to_string()]);
let loc_b = MemoryLocation::new(200, 8); assert_eq!(aa.may_alias(&loc_a, &loc_b), AliasResult::MayAlias);
}
#[test]
fn test_scev_expr_add_display() {
let a = X86SCEVExpr::constant(3);
let b = X86SCEVExpr::constant(4);
let sum = X86SCEVExpr::Add(Box::new(a), Box::new(b));
assert_eq!(format!("{}", sum), "(3 + 4)");
}
#[test]
fn test_scev_expr_mul_display() {
let a = X86SCEVExpr::constant(3);
let b = X86SCEVExpr::constant(4);
let prod = X86SCEVExpr::Mul(Box::new(a), Box::new(b));
assert_eq!(format!("{}", prod), "(3 * 4)");
}
#[test]
fn test_tbaa_node_children_tracking() {
let mut tbaa = X86TypeBasedAA::new();
tbaa.add_type_node(TBAANode::new(1, 0, "root"));
tbaa.add_type_node(TBAANode::new(2, 1, "child1"));
tbaa.add_type_node(TBAANode::new(3, 1, "child2"));
let root = tbaa.type_nodes.get(&1).unwrap();
assert_eq!(root.children.len(), 2);
assert!(root.children.contains(&2));
assert!(root.children.contains(&3));
}
#[test]
fn test_globals_mod_ref_argmemonly() {
let mut gmr = X86GlobalsModRef::new();
gmr.register_function(100, FunctionModRefInfo::OnlyAccessesArgMemory);
let loc = MemoryLocation::new(500, 8);
let result = gmr.get_mod_ref_for_call(100, &loc);
assert_eq!(result, ModRefInfo::ModRef);
}
#[test]
fn test_alias_set_volatile() {
let mut set = AliasSet::new(1);
assert!(!set.is_volatile);
set.is_volatile = true;
assert!(set.is_volatile);
let mut set2 = AliasSet::new(2);
set2.merge(&set);
assert!(set2.is_volatile);
}
}