weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
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);
}