use super::*;
#[test]
fn fixed_point_resident_batch_reuses_graph_and_frontier() {
let backend = FakeResidentBackend::new();
let graph = graph_fixture();
let pipeline = Arc::new(FakeResidentPipeline::new());
let mut batch = FixedPointResidentBatch::new();
let first = batch
.reaching(
&backend,
pipeline.clone(),
&graph,
&[0b01],
1,
&DispatchConfig::default(),
)
.expect("first resident batch solve must succeed");
let second = batch
.reaching(
&backend,
pipeline.clone(),
&graph,
&[0b01],
1,
&DispatchConfig::default(),
)
.expect("second resident batch solve must reuse graph and frontier");
assert_eq!(first, vec![0b01]);
assert_eq!(second, vec![0b01]);
assert_eq!(
pipeline.seen_resident_prefix.lock().unwrap().as_slice(),
&[7, 7],
"both solves must dispatch five graph resources plus two frontier resources"
);
assert_eq!(
backend.uploads.lock().unwrap().len(),
8,
"two solves must upload four graph buffers once plus frontier_in and frontier_out seed buffers per solve"
);
assert_eq!(
backend.batch_uploads.load(Ordering::Relaxed),
3,
"resident fixed-point hot path must batch one graph upload and one seed-frontier upload per solve"
);
assert_eq!(
batch.stats(),
FixedPointResidentBatchStats {
graph_cache: FixedPointResidentGraphCacheStats {
hits: 1,
misses: 1,
resident_uploads: 1,
resident_upload_bytes: batch.graph_cache_stats().retained_bytes as u64,
resident_avoided_upload_bytes: batch.graph_cache_stats().retained_bytes as u64,
evictions: 0,
retained_bytes: batch.graph_cache_stats().retained_bytes,
entries: 1,
},
execution_plan_cache: FixedPointExecutionPlanCacheStats {
hits: 1,
misses: 1,
evictions: 0,
entries: 1,
},
frontier_allocations: 1,
frontier_reuses: 1,
frontier_clears: 0,
resident_dispatches: 2,
frontier_upload_bytes: 16,
result_readback_bytes: 8,
resident_graph_cold_uploads: 1,
resident_graph_warm_reuses: 1,
resident_graph_upload_bytes: batch.graph_cache_stats().retained_bytes as u64,
resident_graph_avoided_upload_bytes: batch.graph_cache_stats().retained_bytes as u64,
frontier_resident: true,
frontier_bytes: 4,
}
);
let plan = batch
.execution_plan_for_prepared_graph(&graph)
.expect("resident fixed-point batch execution plan must fit host byte accounting");
assert_eq!(plan.node_count, 2);
assert_eq!(
plan.execution_mode,
crate::fixed_point_scratch::FrontierExecutionMode::Dense
);
assert_eq!(batch.execution_plan_cache_stats().misses, 1);
assert_eq!(batch.execution_plan_cache_stats().hits, 1);
assert_eq!(batch.execution_plan_cache_stats().entries, 1);
batch
.free_all(&backend)
.expect("resident fixed-point batch must free resources");
assert_eq!(
backend.freed.lock().unwrap().len(),
6,
"free_all must release two frontier resources plus four unique cached graph resources"
);
assert_eq!(batch.stats().graph_cache.retained_bytes, 0);
assert!(!batch.stats().frontier_resident);
}
#[test]
fn fixed_point_resident_batch_reuses_larger_frontier_for_smaller_graph() {
let backend = FakeResidentBackend::new();
let large_graph = empty_graph_fixture(64);
let small_graph = graph_fixture();
let pipeline = Arc::new(FakeSequenceResidentPipeline::new([
vec![1, 0, 0, 0, 0, 0, 0, 0],
vec![1, 0, 0, 0],
]));
let mut batch = FixedPointResidentBatch::new();
let large = batch
.reaching(
&backend,
pipeline.clone(),
&large_graph,
&[0b01, 0],
1,
&DispatchConfig::default(),
)
.expect("large resident batch solve must allocate frontier capacity");
let small = batch
.reaching(
&backend,
pipeline,
&small_graph,
&[0b01],
1,
&DispatchConfig::default(),
)
.expect("smaller resident batch solve must reuse larger frontier capacity");
assert_eq!(large, vec![0b01, 0]);
assert_eq!(small, vec![0b01]);
assert_eq!(batch.stats().frontier_allocations, 1);
assert_eq!(batch.stats().frontier_reuses, 1);
assert_eq!(
batch.stats().frontier_bytes,
8,
"Fix: resident frontier scratch should keep the larger allocation capacity for later smaller analyses."
);
assert!(
backend.freed.lock().unwrap().is_empty(),
"Fix: smaller resident frontier requests must not free the larger reusable frontier pair."
);
}
#[test]
fn fixed_point_resident_batch_honors_execution_plan_cache_bound() {
let backend = FakeResidentBackend::new();
let reaching_graph = graph_fixture();
let live_graph = graph_fixture_for_kind(crate::fixed_point_graph::FixedPointAnalysisKind::Live);
let pipeline = Arc::new(FakeResidentPipeline::new());
let mut batch = FixedPointResidentBatch::with_cache_bounds(usize::MAX, 1);
batch
.reaching(
&backend,
pipeline.clone(),
&reaching_graph,
&[0b01],
1,
&DispatchConfig::default(),
)
.expect("reaching resident batch solve must succeed");
batch
.live(
&backend,
pipeline,
&live_graph,
&[0b01],
1,
&DispatchConfig::default(),
)
.expect("live resident batch solve must succeed");
let stats = batch.execution_plan_cache_stats();
assert_eq!(stats.entries, 1);
assert_eq!(stats.evictions, 1);
assert_eq!(stats.misses, 2);
}