use crate::fixed_point_graph::FixedPointAnalysisKind;
use std::cmp::Ordering;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ResidentGraphCacheDomain {
IfdsCsr,
FixedPoint(FixedPointAnalysisKind),
}
impl ResidentGraphCacheDomain {
#[must_use]
pub const fn code(self) -> u8 {
match self {
Self::IfdsCsr => 1,
Self::FixedPoint(FixedPointAnalysisKind::Generic) => 16,
Self::FixedPoint(FixedPointAnalysisKind::Reaching) => 17,
Self::FixedPoint(FixedPointAnalysisKind::Live) => 18,
Self::FixedPoint(FixedPointAnalysisKind::PointsToSubset) => 19,
Self::FixedPoint(FixedPointAnalysisKind::Slice) => 20,
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ResidentGraphCacheIdentity {
pub domain: ResidentGraphCacheDomain,
pub backend_id: &'static str,
pub backend_version: &'static str,
pub layout_hash: u64,
pub node_count: u32,
pub edge_count: u32,
pub frontier_words: u32,
}
impl ResidentGraphCacheIdentity {
#[must_use]
pub const fn ifds_csr(
backend_id: &'static str,
backend_version: &'static str,
layout_hash: u64,
node_count: u32,
edge_count: u32,
frontier_words: u32,
) -> Self {
Self {
domain: ResidentGraphCacheDomain::IfdsCsr,
backend_id,
backend_version,
layout_hash,
node_count,
edge_count,
frontier_words,
}
}
#[must_use]
pub const fn fixed_point(
backend_id: &'static str,
backend_version: &'static str,
analysis_kind: FixedPointAnalysisKind,
layout_hash: u64,
node_count: u32,
edge_count: u32,
frontier_words: u32,
) -> Self {
Self {
domain: ResidentGraphCacheDomain::FixedPoint(analysis_kind),
backend_id,
backend_version,
layout_hash,
node_count,
edge_count,
frontier_words,
}
}
#[must_use]
pub fn stable_digest64(&self) -> u64 {
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
fn mix(mut hash: u64, bytes: &[u8]) -> u64 {
for byte in bytes {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
let mut hash = FNV_OFFSET;
hash = mix(hash, b"weir-resident-graph-cache-identity-v1\0domain\0");
hash = mix(hash, &[self.domain.code()]);
hash = mix(hash, b"\0backend-id\0");
hash = mix(hash, self.backend_id.as_bytes());
hash = mix(hash, b"\0backend-version\0");
hash = mix(hash, self.backend_version.as_bytes());
hash = mix(hash, b"\0layout\0");
hash = mix(hash, &self.layout_hash.to_le_bytes());
hash = mix(hash, b"\0nodes\0");
hash = mix(hash, &self.node_count.to_le_bytes());
hash = mix(hash, b"\0edges\0");
hash = mix(hash, &self.edge_count.to_le_bytes());
hash = mix(hash, b"\0frontier-words\0");
mix(hash, &self.frontier_words.to_le_bytes())
}
#[must_use]
pub fn cmp_stable(&self, other: &Self) -> Ordering {
(
self.domain.code(),
self.backend_id,
self.backend_version,
self.layout_hash,
self.node_count,
self.edge_count,
self.frontier_words,
)
.cmp(&(
other.domain.code(),
other.backend_id,
other.backend_version,
other.layout_hash,
other.node_count,
other.edge_count,
other.frontier_words,
))
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct ResidentGraphCacheMissEvidence {
pub total_entries: usize,
pub same_domain_entries: usize,
pub same_backend_entries: usize,
pub same_layout_entries: usize,
pub same_shape_entries: usize,
}
impl ResidentGraphCacheMissEvidence {
#[must_use]
pub fn from_identities<'a>(
cached: impl Iterator<Item = &'a ResidentGraphCacheIdentity>,
requested: &ResidentGraphCacheIdentity,
) -> Self {
let mut evidence = Self::default();
for identity in cached {
evidence.total_entries += 1;
if identity.domain == requested.domain {
evidence.same_domain_entries += 1;
if identity.backend_id == requested.backend_id
&& identity.backend_version == requested.backend_version
{
evidence.same_backend_entries += 1;
if identity.layout_hash == requested.layout_hash {
evidence.same_layout_entries += 1;
if identity.node_count == requested.node_count
&& identity.edge_count == requested.edge_count
&& identity.frontier_words == requested.frontier_words
{
evidence.same_shape_entries += 1;
}
}
}
}
}
evidence
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ResidentGraphCacheMissReason {
EmptyCache,
DomainChanged,
BackendChanged,
LayoutChanged,
ShapeChanged,
KeyAbsent,
}
impl ResidentGraphCacheMissReason {
#[must_use]
pub const fn classify(evidence: ResidentGraphCacheMissEvidence) -> Self {
if evidence.total_entries == 0 {
Self::EmptyCache
} else if evidence.same_domain_entries == 0 {
Self::DomainChanged
} else if evidence.same_backend_entries == 0 {
Self::BackendChanged
} else if evidence.same_layout_entries == 0 {
Self::LayoutChanged
} else if evidence.same_shape_entries == 0 {
Self::ShapeChanged
} else {
Self::KeyAbsent
}
}
#[must_use]
pub const fn metric_suffix(self) -> &'static str {
match self {
Self::EmptyCache => "empty_cache",
Self::DomainChanged => "domain_changed",
Self::BackendChanged => "backend_changed",
Self::LayoutChanged => "layout_changed",
Self::ShapeChanged => "shape_changed",
Self::KeyAbsent => "key_absent",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ifds_identity() -> ResidentGraphCacheIdentity {
ResidentGraphCacheIdentity::ifds_csr("backend-a", "v1", 0x1111, 64, 8, 2)
}
#[test]
fn resident_graph_identity_digest_separates_cache_compatibility_fields() {
let base = ifds_identity();
assert_ne!(
base.stable_digest64(),
ResidentGraphCacheIdentity::ifds_csr("backend-b", "v1", 0x1111, 64, 8, 2)
.stable_digest64()
);
assert_ne!(
base.stable_digest64(),
ResidentGraphCacheIdentity::ifds_csr("backend-a", "v2", 0x1111, 64, 8, 2)
.stable_digest64()
);
assert_ne!(
base.stable_digest64(),
ResidentGraphCacheIdentity::ifds_csr("backend-a", "v1", 0x2222, 64, 8, 2)
.stable_digest64()
);
assert_ne!(
base.stable_digest64(),
ResidentGraphCacheIdentity::ifds_csr("backend-a", "v1", 0x1111, 64, 8, 3)
.stable_digest64()
);
assert_ne!(
base.stable_digest64(),
ResidentGraphCacheIdentity::fixed_point(
"backend-a",
"v1",
FixedPointAnalysisKind::Reaching,
0x1111,
64,
8,
2,
)
.stable_digest64()
);
}
#[test]
fn resident_graph_miss_reason_classifies_adjacent_identity() {
let requested = ifds_identity();
assert_eq!(
ResidentGraphCacheMissReason::classify(
ResidentGraphCacheMissEvidence::from_identities([].iter(), &requested)
),
ResidentGraphCacheMissReason::EmptyCache
);
let different_domain = ResidentGraphCacheIdentity::fixed_point(
"backend-a",
"v1",
FixedPointAnalysisKind::Reaching,
0x1111,
64,
8,
2,
);
assert_eq!(
ResidentGraphCacheMissReason::classify(
ResidentGraphCacheMissEvidence::from_identities(
std::iter::once(&different_domain),
&requested,
)
),
ResidentGraphCacheMissReason::DomainChanged
);
let different_backend =
ResidentGraphCacheIdentity::ifds_csr("backend-b", "v1", 0x1111, 64, 8, 2);
assert_eq!(
ResidentGraphCacheMissReason::classify(
ResidentGraphCacheMissEvidence::from_identities(
std::iter::once(&different_backend),
&requested,
)
),
ResidentGraphCacheMissReason::BackendChanged
);
let different_layout =
ResidentGraphCacheIdentity::ifds_csr("backend-a", "v1", 0x2222, 64, 8, 2);
assert_eq!(
ResidentGraphCacheMissReason::classify(
ResidentGraphCacheMissEvidence::from_identities(
std::iter::once(&different_layout),
&requested,
)
),
ResidentGraphCacheMissReason::LayoutChanged
);
let different_shape =
ResidentGraphCacheIdentity::ifds_csr("backend-a", "v1", 0x1111, 64, 8, 3);
assert_eq!(
ResidentGraphCacheMissReason::classify(
ResidentGraphCacheMissEvidence::from_identities(
std::iter::once(&different_shape),
&requested,
)
),
ResidentGraphCacheMissReason::ShapeChanged
);
assert_eq!(
ResidentGraphCacheMissReason::classify(
ResidentGraphCacheMissEvidence::from_identities(std::iter::once(&requested), &requested)
),
ResidentGraphCacheMissReason::KeyAbsent
);
}
}