use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AliasPosition {
Param(u32),
Return,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum AliasKind {
MayAlias,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct AliasEdge {
pub source: AliasPosition,
pub target: AliasPosition,
pub kind: AliasKind,
}
pub const MAX_ALIAS_EDGES: usize = 8;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PointsToSummary {
#[serde(default, skip_serializing_if = "SmallVec::is_empty")]
pub edges: SmallVec<[AliasEdge; 4]>,
#[serde(default, skip_serializing_if = "core::ops::Not::not")]
pub overflow: bool,
#[serde(default, skip_serializing_if = "core::ops::Not::not")]
pub returns_fresh_alloc: bool,
}
impl PointsToSummary {
pub fn empty() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.edges.is_empty() && !self.overflow && !self.returns_fresh_alloc
}
pub fn insert(&mut self, source: AliasPosition, target: AliasPosition, kind: AliasKind) {
if self.overflow {
return;
}
let edge = AliasEdge {
source,
target,
kind,
};
if self.edges.contains(&edge) {
return;
}
if self.edges.len() >= MAX_ALIAS_EDGES {
self.overflow = true;
return;
}
self.edges.push(edge);
}
pub fn merge(&mut self, other: &Self) {
self.returns_fresh_alloc |= other.returns_fresh_alloc;
if other.overflow {
self.overflow = true;
return;
}
for edge in &other.edges {
self.insert(edge.source, edge.target, edge.kind);
}
}
pub fn max_param_index(&self) -> Option<u32> {
let mut max: Option<u32> = None;
for edge in &self.edges {
if let AliasPosition::Param(i) = edge.source {
max = Some(max.map_or(i, |m| m.max(i)));
}
if let AliasPosition::Param(i) = edge.target {
max = Some(max.map_or(i, |m| m.max(i)));
}
}
max
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_summary_is_noop() {
let s = PointsToSummary::empty();
assert!(s.is_empty());
assert!(!s.overflow);
assert_eq!(s.edges.len(), 0);
}
#[test]
fn insert_dedups() {
let mut s = PointsToSummary::empty();
s.insert(
AliasPosition::Param(0),
AliasPosition::Param(1),
AliasKind::MayAlias,
);
s.insert(
AliasPosition::Param(0),
AliasPosition::Param(1),
AliasKind::MayAlias,
);
assert_eq!(s.edges.len(), 1);
}
#[test]
fn insert_overflows_at_cap() {
let mut s = PointsToSummary::empty();
for i in 0..(MAX_ALIAS_EDGES as u32) {
s.insert(
AliasPosition::Param(i),
AliasPosition::Return,
AliasKind::MayAlias,
);
}
assert_eq!(s.edges.len(), MAX_ALIAS_EDGES);
assert!(!s.overflow);
s.insert(
AliasPosition::Param(99),
AliasPosition::Return,
AliasKind::MayAlias,
);
assert!(s.overflow);
assert_eq!(s.edges.len(), MAX_ALIAS_EDGES);
}
#[test]
fn merge_propagates_overflow() {
let mut a = PointsToSummary::empty();
let mut b = PointsToSummary::empty();
b.overflow = true;
a.merge(&b);
assert!(a.overflow);
}
#[test]
fn max_param_index_tracks_both_endpoints() {
let mut s = PointsToSummary::empty();
s.insert(
AliasPosition::Param(0),
AliasPosition::Param(3),
AliasKind::MayAlias,
);
s.insert(
AliasPosition::Param(1),
AliasPosition::Return,
AliasKind::MayAlias,
);
assert_eq!(s.max_param_index(), Some(3));
}
#[test]
fn serde_round_trip_is_stable() {
let mut s = PointsToSummary::empty();
s.insert(
AliasPosition::Param(0),
AliasPosition::Param(1),
AliasKind::MayAlias,
);
s.insert(
AliasPosition::Param(2),
AliasPosition::Return,
AliasKind::MayAlias,
);
let json = serde_json::to_string(&s).unwrap();
let back: PointsToSummary = serde_json::from_str(&json).unwrap();
assert_eq!(s, back);
}
#[test]
fn serde_default_decodes_empty_object() {
let back: PointsToSummary = serde_json::from_str("{}").unwrap();
assert!(back.is_empty());
}
#[test]
fn returns_fresh_alloc_is_not_empty() {
let mut s = PointsToSummary::empty();
assert!(s.is_empty());
s.returns_fresh_alloc = true;
assert!(!s.is_empty());
}
#[test]
fn merge_propagates_fresh_alloc_flag() {
let mut a = PointsToSummary::empty();
let mut b = PointsToSummary::empty();
b.returns_fresh_alloc = true;
a.merge(&b);
assert!(a.returns_fresh_alloc);
}
#[test]
fn returns_fresh_alloc_roundtrips() {
let mut s = PointsToSummary::empty();
s.returns_fresh_alloc = true;
let json = serde_json::to_string(&s).unwrap();
let back: PointsToSummary = serde_json::from_str(&json).unwrap();
assert!(back.returns_fresh_alloc);
assert_eq!(s, back);
}
}
pub const MAX_FIELDS_PER_PARAM: usize = 8;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct FieldPointsToSummary {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_field_reads: Vec<(u32, SmallVec<[String; 2]>)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub param_field_writes: Vec<(u32, SmallVec<[String; 2]>)>,
#[serde(default, skip_serializing_if = "core::ops::Not::not")]
pub overflow: bool,
}
impl FieldPointsToSummary {
pub fn empty() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.param_field_reads.is_empty() && self.param_field_writes.is_empty() && !self.overflow
}
fn insert_into(
list: &mut Vec<(u32, SmallVec<[String; 2]>)>,
param: u32,
field: &str,
overflow: &mut bool,
) {
let entry = match list.iter_mut().find(|(p, _)| *p == param) {
Some(e) => &mut e.1,
None => {
list.push((param, SmallVec::new()));
&mut list.last_mut().unwrap().1
}
};
if entry.iter().any(|s| s == field) {
return;
}
if entry.len() >= MAX_FIELDS_PER_PARAM {
*overflow = true;
return;
}
entry.push(field.to_string());
entry.sort();
}
pub fn add_read(&mut self, param: u32, field: &str) {
if self.overflow {
return;
}
let mut overflow = false;
Self::insert_into(&mut self.param_field_reads, param, field, &mut overflow);
if overflow {
self.overflow = true;
}
}
pub fn add_write(&mut self, param: u32, field: &str) {
if self.overflow {
return;
}
let mut overflow = false;
Self::insert_into(&mut self.param_field_writes, param, field, &mut overflow);
if overflow {
self.overflow = true;
}
}
pub fn merge(&mut self, other: &Self) {
if other.overflow {
self.overflow = true;
return;
}
for (p, fields) in &other.param_field_reads {
for f in fields {
self.add_read(*p, f);
}
}
for (p, fields) in &other.param_field_writes {
for f in fields {
self.add_write(*p, f);
}
}
}
}
#[cfg(test)]
mod field_summary_tests {
use super::*;
#[test]
fn empty_summary_round_trips() {
let s = FieldPointsToSummary::empty();
assert!(s.is_empty());
let json = serde_json::to_string(&s).unwrap();
let back: FieldPointsToSummary = serde_json::from_str(&json).unwrap();
assert_eq!(s, back);
}
#[test]
fn add_read_dedupes_and_sorts() {
let mut s = FieldPointsToSummary::empty();
s.add_read(0, "name");
s.add_read(0, "id");
s.add_read(0, "name"); let entry = s.param_field_reads.iter().find(|(p, _)| *p == 0).unwrap();
assert_eq!(entry.1.as_slice(), &["id".to_string(), "name".to_string()]);
}
#[test]
fn distinct_params_get_distinct_entries() {
let mut s = FieldPointsToSummary::empty();
s.add_write(0, "cache");
s.add_write(1, "log");
assert_eq!(s.param_field_writes.len(), 2);
}
#[test]
fn overflow_trips_at_cap() {
let mut s = FieldPointsToSummary::empty();
for i in 0..(MAX_FIELDS_PER_PARAM + 4) {
s.add_read(0, &format!("field{i}"));
}
assert!(s.overflow);
}
#[test]
fn merge_unions_disjoint_keys() {
let mut a = FieldPointsToSummary::empty();
let mut b = FieldPointsToSummary::empty();
a.add_read(0, "alpha");
b.add_read(1, "beta");
a.merge(&b);
assert!(a.param_field_reads.iter().any(|(p, _)| *p == 0));
assert!(a.param_field_reads.iter().any(|(p, _)| *p == 1));
}
#[test]
fn merge_propagates_overflow() {
let mut a = FieldPointsToSummary::empty();
let mut b = FieldPointsToSummary::empty();
b.overflow = true;
a.merge(&b);
assert!(a.overflow);
}
#[test]
fn round_trip_preserves_entries() {
let mut s = FieldPointsToSummary::empty();
s.add_read(0, "name");
s.add_write(1, "cache");
s.add_write(1, "log");
let json = serde_json::to_string(&s).unwrap();
let back: FieldPointsToSummary = serde_json::from_str(&json).unwrap();
assert_eq!(s, back);
}
#[test]
fn empty_serializes_as_empty_object() {
let s = FieldPointsToSummary::empty();
let json = serde_json::to_string(&s).unwrap();
assert_eq!(json, "{}");
let back: FieldPointsToSummary = serde_json::from_str("{}").unwrap();
assert!(back.is_empty());
}
}