use crate::cfg::BodyId;
use crate::ssa::ir::FieldId;
use smallvec::SmallVec;
use std::collections::HashMap;
pub const MAX_FIELD_DEPTH: u8 = 3;
pub const MAX_POINTSTO_MEMBERS: usize = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LocId(pub u32);
pub const LOC_TOP: LocId = LocId(0);
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum AbsLoc {
Top,
Alloc(BodyId, u32),
Param(BodyId, usize),
SelfParam(BodyId),
Field { parent: LocId, field: FieldId },
}
#[derive(Clone, Debug)]
pub struct LocInterner {
locs: Vec<AbsLoc>,
alloc_lookup: HashMap<(BodyId, u32), LocId>,
param_lookup: HashMap<(BodyId, usize), LocId>,
self_param_lookup: HashMap<BodyId, LocId>,
field_lookup: HashMap<(LocId, FieldId), LocId>,
depths: Vec<u8>,
}
impl Default for LocInterner {
fn default() -> Self {
Self::new()
}
}
impl LocInterner {
pub fn new() -> Self {
Self {
locs: vec![AbsLoc::Top],
alloc_lookup: HashMap::new(),
param_lookup: HashMap::new(),
self_param_lookup: HashMap::new(),
field_lookup: HashMap::new(),
depths: vec![0],
}
}
#[inline]
pub fn len(&self) -> usize {
self.locs.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.locs.len() <= 1
}
#[inline]
pub fn resolve(&self, id: LocId) -> &AbsLoc {
&self.locs[id.0 as usize]
}
#[inline]
pub fn depth(&self, id: LocId) -> u8 {
self.depths[id.0 as usize]
}
pub fn intern_alloc(&mut self, body: BodyId, ssa_value: u32) -> LocId {
if let Some(&id) = self.alloc_lookup.get(&(body, ssa_value)) {
return id;
}
let id = self.push(AbsLoc::Alloc(body, ssa_value), 0);
self.alloc_lookup.insert((body, ssa_value), id);
id
}
pub fn intern_param(&mut self, body: BodyId, index: usize) -> LocId {
if let Some(&id) = self.param_lookup.get(&(body, index)) {
return id;
}
let id = self.push(AbsLoc::Param(body, index), 0);
self.param_lookup.insert((body, index), id);
id
}
pub fn intern_self_param(&mut self, body: BodyId) -> LocId {
if let Some(&id) = self.self_param_lookup.get(&body) {
return id;
}
let id = self.push(AbsLoc::SelfParam(body), 0);
self.self_param_lookup.insert(body, id);
id
}
pub fn intern_field(&mut self, parent: LocId, field: FieldId) -> LocId {
if parent == LOC_TOP {
return LOC_TOP;
}
let parent_depth = self.depth(parent);
if parent_depth >= MAX_FIELD_DEPTH {
return LOC_TOP;
}
let key = (parent, field);
if let Some(&id) = self.field_lookup.get(&key) {
return id;
}
let id = self.push(AbsLoc::Field { parent, field }, parent_depth + 1);
self.field_lookup.insert(key, id);
id
}
fn push(&mut self, loc: AbsLoc, depth: u8) -> LocId {
let id = LocId(self.locs.len() as u32);
self.locs.push(loc);
self.depths.push(depth);
id
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PtrProxyHint {
FieldOnly,
Other,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PointsToSet {
ids: SmallVec<[LocId; 4]>,
}
impl Default for PointsToSet {
fn default() -> Self {
Self::empty()
}
}
impl PointsToSet {
pub fn empty() -> Self {
Self {
ids: SmallVec::new(),
}
}
pub fn singleton(id: LocId) -> Self {
let mut ids = SmallVec::new();
ids.push(id);
Self { ids }
}
pub fn top() -> Self {
Self::singleton(LOC_TOP)
}
pub fn is_top(&self) -> bool {
self.ids.contains(&LOC_TOP)
}
pub fn is_empty(&self) -> bool {
self.ids.is_empty()
}
pub fn len(&self) -> usize {
self.ids.len()
}
pub fn iter(&self) -> impl Iterator<Item = LocId> + '_ {
self.ids.iter().copied()
}
pub fn contains(&self, id: LocId) -> bool {
if self.is_top() {
return true;
}
self.ids.binary_search(&id).is_ok()
}
pub fn insert(&mut self, id: LocId) {
if self.is_top() {
return;
}
if id == LOC_TOP {
self.ids.clear();
self.ids.push(LOC_TOP);
return;
}
match self.ids.binary_search(&id) {
Ok(_) => {}
Err(pos) => {
if self.ids.len() >= MAX_POINTSTO_MEMBERS {
self.ids.clear();
self.ids.push(LOC_TOP);
} else {
self.ids.insert(pos, id);
}
}
}
}
pub fn union_in_place(&mut self, other: &PointsToSet) -> bool {
if self.is_top() {
return false;
}
if other.is_top() {
let was_top = self.is_top();
self.ids.clear();
self.ids.push(LOC_TOP);
return !was_top;
}
let mut changed = false;
for id in other.iter() {
if id == LOC_TOP {
let was_top = self.is_top();
self.ids.clear();
self.ids.push(LOC_TOP);
return !was_top;
}
match self.ids.binary_search(&id) {
Ok(_) => {}
Err(pos) => {
if self.ids.len() >= MAX_POINTSTO_MEMBERS {
self.ids.clear();
self.ids.push(LOC_TOP);
return true;
}
self.ids.insert(pos, id);
changed = true;
}
}
}
changed
}
}
#[cfg(test)]
mod tests {
use super::*;
fn body() -> BodyId {
BodyId(0)
}
#[test]
fn loc_top_is_zero() {
let interner = LocInterner::new();
assert_eq!(interner.len(), 1);
assert_eq!(interner.resolve(LOC_TOP), &AbsLoc::Top);
}
#[test]
fn alloc_intern_dedupes() {
let mut interner = LocInterner::new();
let a = interner.intern_alloc(body(), 7);
let b = interner.intern_alloc(body(), 7);
let c = interner.intern_alloc(body(), 8);
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn param_intern_dedupes_by_index() {
let mut interner = LocInterner::new();
let p0 = interner.intern_param(body(), 0);
let p1 = interner.intern_param(body(), 1);
let p0_again = interner.intern_param(body(), 0);
assert_eq!(p0, p0_again);
assert_ne!(p0, p1);
}
#[test]
fn field_intern_dedupes_structurally() {
let mut interner = LocInterner::new();
let parent = interner.intern_self_param(body());
let f = FieldId(7);
let a = interner.intern_field(parent, f);
let b = interner.intern_field(parent, f);
assert_eq!(a, b, "same parent + same field id ⇒ same loc id");
}
#[test]
fn field_chain_depth_bounded() {
let mut interner = LocInterner::new();
let mut cur = interner.intern_self_param(body());
let f = FieldId(1);
for _ in 0..MAX_FIELD_DEPTH {
cur = interner.intern_field(cur, f);
assert_ne!(cur, LOC_TOP, "depth ≤ MAX should not fold");
}
let folded = interner.intern_field(cur, f);
assert_eq!(folded, LOC_TOP, "exceeding MAX_FIELD_DEPTH folds to Top");
}
#[test]
fn field_of_top_is_top() {
let mut interner = LocInterner::new();
let folded = interner.intern_field(LOC_TOP, FieldId(0));
assert_eq!(folded, LOC_TOP);
}
#[test]
fn pointsto_set_empty_singleton_top() {
assert!(PointsToSet::empty().is_empty());
assert!(PointsToSet::top().is_top());
let mut interner = LocInterner::new();
let p = interner.intern_self_param(body());
let s = PointsToSet::singleton(p);
assert!(s.contains(p));
assert!(!s.is_top());
}
#[test]
fn pointsto_set_insert_and_union() {
let mut interner = LocInterner::new();
let p0 = interner.intern_param(body(), 0);
let p1 = interner.intern_param(body(), 1);
let mut a = PointsToSet::singleton(p0);
let b = PointsToSet::singleton(p1);
let changed = a.union_in_place(&b);
assert!(changed);
assert_eq!(a.len(), 2);
assert!(a.contains(p0));
assert!(a.contains(p1));
let changed2 = a.union_in_place(&b);
assert!(!changed2);
}
#[test]
fn pointsto_set_saturates_to_top_on_overflow() {
let mut interner = LocInterner::new();
let mut s = PointsToSet::empty();
for i in 0..(MAX_POINTSTO_MEMBERS as u32 + 4) {
s.insert(interner.intern_alloc(body(), i));
}
assert!(s.is_top(), "set should collapse to {{Top}} on overflow");
}
#[test]
fn pointsto_set_union_with_top_is_top() {
let mut interner = LocInterner::new();
let p = interner.intern_param(body(), 0);
let mut a = PointsToSet::singleton(p);
let changed = a.union_in_place(&PointsToSet::top());
assert!(changed);
assert!(a.is_top());
}
}