weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
use super::*;

#[test]
fn resident_scratch_can_be_reused_across_independent_solves() {
    let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(1);
    let prepared = ResidentPreparedIfdsCsr {
        shape: IfdsShape {
            num_procs: 1,
            blocks_per_proc: 4,
            facts_per_proc: 1,
            edge_count: 0,
        },
        node_count: 4,
        words: 1,
        pg_nodes: 0,
        row_ptr: 1,
        col_idx: 2,
        pg_edge_kind_mask: 3,
        pg_node_tags: 4,
    };
    let scratch = allocate_resident_ifds_scratch(&dispatch, &prepared)
        .expect("scratch allocation should succeed");
    assert_eq!(
        dispatch.buffers.borrow().len(),
        7,
        "scratch allocation should create only frontier and changed buffers"
    );
    let allocate_calls_after_scratch = dispatch.allocate_calls.get();
    let seed_a = [(0, 0, 0)];
    let seed_b = [(0, 2, 0)];
    let mut host_scratch = ResidentIfdsHostScratch::default();
    let mut results = Vec::with_capacity(1);
    solve_resident_prepared_many_with_scratch_and_host_into_via(
        &dispatch,
        &prepared,
        &scratch,
        &mut host_scratch,
        &[&seed_a[..]],
        8,
        &mut results,
    )
    .expect("first scratch solve should converge");
    assert_eq!(results, vec![vec![0]]);
    let outer_ptr = results.as_ptr();
    let row_ptr = results[0].as_ptr();
    let row_capacity = results[0].capacity();
    let frontier_ptr = host_scratch.frontier.as_ptr();
    let frontier_bytes_ptr = host_scratch.frontier_bytes.as_ptr();
    let changed_bytes_ptr = host_scratch.changed_bytes.as_ptr();
    assert!(host_scratch.step_program.is_some());
    solve_resident_prepared_many_with_scratch_and_host_into_via(
        &dispatch,
        &prepared,
        &scratch,
        &mut host_scratch,
        &[&seed_b[..]],
        8,
        &mut results,
    )
    .expect("second scratch solve should converge");
    assert_eq!(results, vec![vec![2048]]);
    assert_eq!(
        results.as_ptr(),
        outer_ptr,
        "caller-owned result Vec shell must be reused across scratch solves"
    );
    assert_eq!(
        results[0].as_ptr(),
        row_ptr,
        "caller-owned result row allocation must be reused across scratch solves"
    );
    assert_eq!(
        results[0].capacity(),
        row_capacity,
        "caller-owned result row capacity must be preserved across scratch solves"
    );
    assert_eq!(
        host_scratch.frontier.as_ptr(),
        frontier_ptr,
        "host frontier decode buffer must be reused across scratch solves"
    );
    assert_eq!(
        host_scratch.frontier_bytes.as_ptr(),
        frontier_bytes_ptr,
        "host frontier readback bytes must be reused across scratch solves"
    );
    assert_eq!(
        host_scratch.changed_bytes.as_ptr(),
        changed_bytes_ptr,
        "host changed-flag readback bytes must be reused across scratch solves"
    );
    assert_eq!(
        dispatch.buffers.borrow().len(),
        7,
        "reusing scratch across independent solves must retain only frontier and changed resident buffers"
    );
    assert_eq!(
        dispatch.allocate_calls.get(),
        allocate_calls_after_scratch,
        "reusing single-query scratch must not allocate transient seed staging buffers"
    );
    free_resident_ifds_scratch(&dispatch, scratch).expect("scratch free should succeed");
}

#[test]
fn resident_scratch_seed_capacity_is_explicit_and_allocation_free() {
    let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(1);
    let prepared = ResidentPreparedIfdsCsr {
        shape: IfdsShape {
            num_procs: 1,
            blocks_per_proc: 4,
            facts_per_proc: 1,
            edge_count: 0,
        },
        node_count: 4,
        words: 1,
        pg_nodes: 0,
        row_ptr: 1,
        col_idx: 2,
        pg_edge_kind_mask: 3,
        pg_node_tags: 4,
    };
    let scratch = allocate_resident_ifds_scratch_with_seed_capacity(&dispatch, &prepared, 2)
        .expect("scratch allocation with two staged seed facts should succeed");
    assert_eq!(scratch.max_seed_facts(), 2);
    assert_eq!(
        dispatch.buffers.borrow().len(),
        7,
        "single-query scratch should allocate only fixed frontier and changed buffers"
    );
    let allocate_calls_after_scratch = dispatch.allocate_calls.get();
    let seeds = [(0, 0, 0), (0, 2, 0)];
    let _ = solve_resident_prepared_many_with_scratch_via(
        &dispatch,
        &prepared,
        &scratch,
        &[&seeds[..]],
        8,
    )
    .expect("scratch solve with two staged seed facts should converge");
    assert_eq!(
        dispatch.allocate_calls.get(),
        allocate_calls_after_scratch,
        "explicit seed-capacity scratch must not allocate transient seed staging buffers"
    );
    free_resident_ifds_scratch(&dispatch, scratch).expect("scratch free should succeed");
}

#[test]
fn resident_scratch_solve_uses_one_changed_readback_after_gpu_iteration_window() {
    let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(6);
    let node_count = 32_768;
    let words = ifds_words(node_count);
    let prepared = ResidentPreparedIfdsCsr {
        shape: IfdsShape {
            num_procs: 1024,
            blocks_per_proc: 32,
            facts_per_proc: 1,
            edge_count: 0,
        },
        node_count,
        words,
        pg_nodes: 0,
        row_ptr: 1,
        col_idx: 2,
        pg_edge_kind_mask: 3,
        pg_node_tags: 4,
    };
    let scratch = allocate_resident_ifds_scratch(&dispatch, &prepared)
        .expect("scratch allocation should succeed");
    let reached = solve_resident_prepared_many_with_scratch_via(
        &dispatch,
        &prepared,
        &scratch,
        &[&[(0, 0, 0)][..]],
        16,
    )
    .expect("scratch solve should converge inside the speculative GPU window");
    assert_eq!(reached, vec![vec![0]]);
    assert_eq!(
            dispatch.dispatches.get(),
            6,
            "single-query scratch solve should stop after GPU convergence instead of exhausting the full budget"
        );
    assert_eq!(
        dispatch.sequence_read_ranges_calls.get(),
        0,
        "single-query scratch solve must avoid the broken resident sequence launcher on CUDA"
    );
    assert_eq!(
            *dispatch.downloads.borrow(),
            vec![4, 4, 4, 4, 4, 4, words * 4],
            "single-query scratch solve must poll one changed flag per GPU step and download one final frontier"
        );
    free_resident_ifds_scratch(&dispatch, scratch).expect("scratch free should succeed");
}