use super::*;
use crate::fixed_point_graph::FixedPointForwardGraph;
use crate::resident_cache_identity::{
ResidentGraphCacheDomain, ResidentGraphCacheIdentity, ResidentGraphCacheMissReason,
};
use std::{
collections::HashSet,
sync::{
atomic::{AtomicU64, Ordering},
Mutex,
},
};
use vyre::{
backend::{private, BackendError, DispatchConfig, Resource},
VyreBackend,
};
use vyre_primitives::bitset::bitset_words;
struct FakeResidentBackend {
version: &'static str,
next: AtomicU64,
batch_uploads: AtomicU64,
uploads: Mutex<Vec<(u64, usize)>>,
freed: Mutex<Vec<u64>>,
supported_ops: HashSet<vyre::ir::OpId>,
}
impl FakeResidentBackend {
fn new() -> Self {
Self::with_version("test-v1")
}
fn with_version(version: &'static str) -> Self {
Self {
version,
next: AtomicU64::new(1),
batch_uploads: AtomicU64::new(0),
uploads: Mutex::new(Vec::new()),
freed: Mutex::new(Vec::new()),
supported_ops: HashSet::new(),
}
}
}
impl private::Sealed for FakeResidentBackend {}
impl vyre::VyreBackend for FakeResidentBackend {
fn id(&self) -> &'static str {
"weir_test_fake_resident_cache"
}
fn version(&self) -> &'static str {
self.version
}
fn supported_ops(&self) -> &HashSet<vyre::ir::OpId> {
&self.supported_ops
}
fn dispatch(
&self,
_program: &vyre::ir::Program,
_inputs: &[Vec<u8>],
_config: &DispatchConfig,
) -> Result<Vec<Vec<u8>>, BackendError> {
unreachable!("resident graph cache tests do not dispatch through host bytes")
}
fn allocate_resident(&self, _byte_len: usize) -> Result<Resource, BackendError> {
Ok(Resource::Resident(
self.next.fetch_add(1, Ordering::Relaxed),
))
}
fn upload_resident(&self, resource: &Resource, bytes: &[u8]) -> Result<(), BackendError> {
let Resource::Resident(handle) = resource else {
panic!("fake backend only uploads resident resources");
};
self.uploads.lock().unwrap().push((*handle, bytes.len()));
Ok(())
}
fn upload_resident_many(&self, uploads: &[(&Resource, &[u8])]) -> Result<(), BackendError> {
self.batch_uploads.fetch_add(1, Ordering::Relaxed);
for &(resource, bytes) in uploads {
self.upload_resident(resource, bytes)?;
}
Ok(())
}
fn free_resident(&self, resource: Resource) -> Result<(), BackendError> {
let Resource::Resident(handle) = resource else {
panic!("fake backend only frees resident resources");
};
self.freed.lock().unwrap().push(handle);
Ok(())
}
}
fn retained_graph_byte_count(graph: &FixedPointForwardGraph) -> usize {
retained_graph_bytes(graph).expect("fixture retained graph bytes must fit usize")
}
fn fixed_point_identity(
backend: &FakeResidentBackend,
graph: &FixedPointForwardGraph,
) -> ResidentGraphCacheIdentity {
ResidentGraphCacheIdentity::fixed_point(
backend.id(),
backend.version(),
graph.kind(),
graph.stable_layout_hash(),
graph.node_count(),
graph.edge_count(),
bitset_words(graph.node_count()),
)
}
#[test]
fn fixed_point_resident_graph_cache_reuses_equivalent_normalized_layouts() {
let backend = FakeResidentBackend::new();
let unsorted_with_duplicate = FixedPointForwardGraph::new(
"fixed_point_resident_cache_test",
3,
&[0, 3, 4, 4],
&[2, 1, 1, 2],
&[
vyre_primitives::predicate::edge_kind::CONTROL,
vyre_primitives::predicate::edge_kind::ASSIGNMENT,
vyre_primitives::predicate::edge_kind::ASSIGNMENT,
vyre_primitives::predicate::edge_kind::CONTROL,
],
)
.expect("valid unsorted graph must normalize");
let already_canonical = FixedPointForwardGraph::new(
"fixed_point_resident_cache_test",
3,
&[0, 2, 3, 3],
&[1, 2, 2],
&[
vyre_primitives::predicate::edge_kind::ASSIGNMENT,
vyre_primitives::predicate::edge_kind::CONTROL,
vyre_primitives::predicate::edge_kind::CONTROL,
],
)
.expect("valid canonical graph must pack");
let mut cache = FixedPointResidentGraphCache::new();
{
let first = cache
.get_or_upload(&backend, &unsorted_with_duplicate)
.expect("first cache lookup must upload resident graph");
assert_eq!(first.node_count(), 3);
assert_eq!(first.edge_count(), 3);
}
{
let second = cache
.get_or_upload(&backend, &already_canonical)
.expect("equivalent cache lookup must reuse resident graph");
assert_eq!(second.node_count(), 3);
assert_eq!(second.edge_count(), 3);
}
assert_eq!(cache.len(), 1);
assert_eq!(
cache.stats(),
FixedPointResidentGraphCacheStats {
hits: 1,
misses: 1,
resident_uploads: 1,
resident_upload_bytes: retained_graph_byte_count(&already_canonical) as u64,
resident_avoided_upload_bytes: retained_graph_byte_count(&already_canonical) as u64,
evictions: 0,
retained_bytes: retained_graph_byte_count(&already_canonical),
entries: 1,
}
);
assert_eq!(
cache.stats().resident_graph_reuse_telemetry(),
vyre::ResidentGraphReuseTelemetry::from_counters(
1,
1,
retained_graph_byte_count(&already_canonical) as u64,
retained_graph_byte_count(&already_canonical) as u64
)
);
assert_eq!(
backend.uploads.lock().unwrap().len(),
4,
"equivalent normalized graphs must not re-upload invariant resident buffers"
);
assert_eq!(
backend.batch_uploads.load(Ordering::Relaxed),
1,
"resident graph upload must batch invariant buffers instead of synchronizing per buffer"
);
cache
.free_all(&backend)
.expect("resident graph cache must free retained graph resources");
assert!(cache.is_empty());
assert_eq!(cache.stats().retained_bytes, 0);
assert_eq!(backend.freed.lock().unwrap().len(), 4);
}
#[test]
fn fixed_point_resident_graph_cache_exposes_shared_identity_and_miss_reason() {
let backend = FakeResidentBackend::new();
let graph = FixedPointForwardGraph::new(
"fixed_point_resident_cache_identity",
3,
&[0, 1, 1, 1],
&[1],
&[vyre_primitives::predicate::edge_kind::ASSIGNMENT],
)
.expect("graph must pack");
let requested = fixed_point_identity(&backend, &graph);
let mut cache = FixedPointResidentGraphCache::new();
assert_eq!(
cache.miss_reason_for_identity(&requested),
ResidentGraphCacheMissReason::EmptyCache
);
cache
.get_or_upload(&backend, &graph)
.expect("first resident graph lookup must upload");
assert_eq!(
cache.last_miss_reason(),
Some(ResidentGraphCacheMissReason::EmptyCache)
);
let identities = cache.resident_identities();
assert_eq!(identities.len(), 1);
assert_eq!(identities[0], requested);
assert_eq!(
identities[0].domain,
ResidentGraphCacheDomain::FixedPoint(graph.kind())
);
assert_eq!(identities[0].backend_id, backend.id());
assert_eq!(identities[0].backend_version, backend.version());
assert_eq!(identities[0].layout_hash, graph.stable_layout_hash());
assert_eq!(identities[0].node_count, graph.node_count());
assert_eq!(identities[0].edge_count, graph.edge_count());
assert_eq!(identities[0].frontier_words, bitset_words(graph.node_count()));
assert_ne!(identities[0].stable_digest64(), 0);
let changed_shape = ResidentGraphCacheIdentity::fixed_point(
backend.id(),
backend.version(),
graph.kind(),
graph.stable_layout_hash(),
graph.node_count(),
graph.edge_count(),
bitset_words(graph.node_count()) + 1,
);
assert_eq!(
cache.miss_reason_for_identity(&changed_shape),
ResidentGraphCacheMissReason::ShapeChanged
);
cache
.get_or_upload(&backend, &graph)
.expect("second resident graph lookup must hit");
assert_eq!(cache.last_miss_reason(), None);
}
#[test]
fn fixed_point_resident_graph_cache_evicts_least_recently_used_graph() {
let backend = FakeResidentBackend::new();
let first = FixedPointForwardGraph::new(
"fixed_point_resident_cache_first",
3,
&[0, 1, 1, 1],
&[1],
&[vyre_primitives::predicate::edge_kind::ASSIGNMENT],
)
.expect("first graph must pack");
let second = FixedPointForwardGraph::new(
"fixed_point_resident_cache_second",
3,
&[0, 0, 1, 1],
&[2],
&[vyre_primitives::predicate::edge_kind::CONTROL],
)
.expect("second graph must pack");
let mut cache =
FixedPointResidentGraphCache::with_max_retained_bytes(retained_graph_byte_count(&first));
cache
.get_or_upload(&backend, &first)
.expect("first graph upload must succeed");
cache
.get_or_upload(&backend, &second)
.expect("second graph upload must evict first graph");
cache
.get_or_upload(&backend, &first)
.expect("third graph upload must evict second graph");
assert_eq!(cache.len(), 1);
assert_eq!(
cache.stats(),
FixedPointResidentGraphCacheStats {
hits: 0,
misses: 3,
resident_uploads: 3,
resident_upload_bytes: (retained_graph_byte_count(&first)
+ retained_graph_byte_count(&second)
+ retained_graph_byte_count(&first)) as u64,
resident_avoided_upload_bytes: 0,
evictions: 2,
retained_bytes: retained_graph_byte_count(&first),
entries: 1,
}
);
assert_eq!(backend.batch_uploads.load(Ordering::Relaxed), 3);
assert_eq!(backend.freed.lock().unwrap().len(), 8);
cache
.free_all(&backend)
.expect("resident graph cache must free final retained graph resources");
assert_eq!(cache.stats().retained_bytes, 0);
assert_eq!(backend.freed.lock().unwrap().len(), 12);
}
#[test]
fn fixed_point_resident_graph_cache_separates_backend_versions() {
let graph = FixedPointForwardGraph::new(
"fixed_point_resident_cache_backend_version",
3,
&[0, 1, 1, 1],
&[1],
&[vyre_primitives::predicate::edge_kind::ASSIGNMENT],
)
.expect("graph must pack");
let first_backend = FakeResidentBackend::with_version("test-v1");
let second_backend = FakeResidentBackend::with_version("test-v2");
let mut cache = FixedPointResidentGraphCache::new();
cache
.get_or_upload(&first_backend, &graph)
.expect("first backend version must upload graph");
cache
.get_or_upload(&second_backend, &graph)
.expect("changed backend version must upload a distinct graph entry");
assert_eq!(
cache.stats(),
FixedPointResidentGraphCacheStats {
hits: 0,
misses: 2,
resident_uploads: 2,
resident_upload_bytes: (retained_graph_byte_count(&graph) * 2) as u64,
resident_avoided_upload_bytes: 0,
evictions: 0,
retained_bytes: retained_graph_byte_count(&graph) * 2,
entries: 2,
}
);
assert_eq!(
first_backend.batch_uploads.load(Ordering::Relaxed),
1,
"Fix: first backend version must receive exactly one resident graph upload."
);
assert_eq!(
second_backend.batch_uploads.load(Ordering::Relaxed),
1,
"Fix: backend version changes must not reuse resident graph buffers from an older implementation."
);
}
#[test]
fn fixed_point_resident_graph_cache_separates_analysis_families() {
let reaching = FixedPointForwardGraph::new_for_kind(
crate::fixed_point_graph::FixedPointAnalysisKind::Reaching,
"fixed_point_resident_cache_analysis_family",
3,
&[0, 1, 1, 1],
&[1],
&[vyre_primitives::predicate::edge_kind::CONTROL],
)
.expect("reaching graph must pack");
let slice = FixedPointForwardGraph::new_for_kind(
crate::fixed_point_graph::FixedPointAnalysisKind::Slice,
"fixed_point_resident_cache_analysis_family",
3,
&[0, 1, 1, 1],
&[1],
&[vyre_primitives::predicate::edge_kind::CONTROL],
)
.expect("slice graph must pack");
assert_eq!(
reaching.stable_layout_hash(),
slice.stable_layout_hash(),
"fixture must isolate analysis-family separation from layout hashing"
);
let backend = FakeResidentBackend::new();
let mut cache = FixedPointResidentGraphCache::new();
{
let resident = cache
.get_or_upload(&backend, &reaching)
.expect("reaching graph upload must succeed");
assert_eq!(
resident.kind(),
crate::fixed_point_graph::FixedPointAnalysisKind::Reaching
);
}
{
let resident = cache
.get_or_upload(&backend, &slice)
.expect("slice graph with same layout must not reuse reaching entry");
assert_eq!(
resident.kind(),
crate::fixed_point_graph::FixedPointAnalysisKind::Slice
);
}
assert_eq!(
cache.stats(),
FixedPointResidentGraphCacheStats {
hits: 0,
misses: 2,
resident_uploads: 2,
resident_upload_bytes: (retained_graph_byte_count(&reaching)
+ retained_graph_byte_count(&slice)) as u64,
resident_avoided_upload_bytes: 0,
evictions: 0,
retained_bytes: retained_graph_byte_count(&reaching)
+ retained_graph_byte_count(&slice),
entries: 2,
}
);
assert_eq!(
backend.batch_uploads.load(Ordering::Relaxed),
2,
"same-layout graphs for different analyses must not share resident handles"
);
}
#[test]
fn fixed_point_resident_graph_cache_lru_heap_compacts_hot_hits() {
let backend = FakeResidentBackend::new();
let graph = FixedPointForwardGraph::new(
"fixed_point_resident_cache_lru_heap_compacts_hot_hits",
3,
&[0, 1, 1, 1],
&[1],
&[vyre_primitives::predicate::edge_kind::ASSIGNMENT],
)
.expect("graph must pack");
let mut cache = FixedPointResidentGraphCache::new();
cache
.get_or_upload(&backend, &graph)
.expect("first graph upload must succeed");
for _ in 0..160 {
cache
.get_or_upload(&backend, &graph)
.expect("hot resident graph cache hit must succeed");
}
assert_eq!(cache.len(), 1);
assert_eq!(cache.stats().hits, 160);
let stale_limit = cache
.len()
.checked_mul(4)
.and_then(|value| value.checked_add(32))
.expect("test resident cache stale LRU limit must fit usize");
assert!(
cache.lru_len_for_tests() <= stale_limit,
"Fix: resident graph cache LRU metadata must compact stale hit records instead of growing with every access."
);
cache
.free_all(&backend)
.expect("resident graph cache must free retained graph resources");
assert_eq!(cache.lru_len_for_tests(), 0);
}
#[test]
fn fixed_point_resident_graph_cache_accounting_uses_checked_arithmetic() {
let source = include_str!("../cache.rs");
for forbidden in [
"retained_bytes.saturating_add",
"retained_bytes.saturating_sub",
"resident_upload_bytes\n .saturating_add",
"resident_avoided_upload_bytes\n .saturating_add",
"self.lru.len() <= self.entries.len().saturating_mul(4).saturating_add(32)",
"BinaryHeap::with_capacity(self.entries.len())",
concat!(".", "expect("),
concat!("panic", "!("),
concat!("unimplemented", "!("),
concat!("todo", "!("),
] {
assert!(
!source.contains(forbidden),
"Fix: resident graph cache accounting must use checked arithmetic, not saturating math that hides budget/accounting corruption: {forbidden}"
);
}
}