weirflow 0.1.0

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

#[test]
fn resident_parallel_batch_solver_dispatches_all_seed_sets_together() {
    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 seed_a = [(0, 0, 0)];
    let seed_b = [(0, 1, 0)];
    let seed_c = [(0, 2, 0)];
    let got = solve_resident_prepared_many_parallel_via(
        &dispatch,
        &prepared,
        &[&seed_a[..], &seed_b[..], &seed_c[..]],
        8,
    )
    .expect("fake resident parallel batch solver should converge");
    assert_eq!(got, vec![vec![0], vec![1024], vec![2048]]);
    assert_eq!(
            dispatch.dispatches.get(),
            8,
            "parallel batch solve must dispatch all seed sets together without host polling between iterations"
        );
    assert_eq!(
            dispatch.sequence_read_ranges_calls.get(),
            1,
            "parallel batch solve must batch the fixed-point window and compact readbacks behind one resident sequence-read call"
        );
    assert_eq!(
            *dispatch.downloads.borrow(),
            vec![4, 12],
            "parallel batch solve should download one final changed flag, then one final frontier matrix"
        );
}

#[test]
fn resident_parallel_batch_convergence_download_is_constant_in_query_count() {
    let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(4);
    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 seed_a = [(0, 0, 0)];
    let seed_b = [(1, 0, 0)];
    let seed_c = [(2, 0, 0)];
    let seed_d = [(3, 0, 0)];
    let seed_e = [(4, 0, 0)];
    let got = solve_resident_prepared_many_parallel_via(
        &dispatch,
        &prepared,
        &[
            &seed_a[..],
            &seed_b[..],
            &seed_c[..],
            &seed_d[..],
            &seed_e[..],
        ],
        8,
    )
    .expect("fake resident parallel batch solver should converge");
    assert_eq!(got.len(), 5);
    assert_eq!(dispatch.dispatches.get(), 8);
    let downloads = dispatch.downloads.borrow();
    assert_eq!(downloads.len(), 2);
    assert_eq!(downloads[0], std::mem::size_of::<u32>());
    assert_eq!(downloads[1], 5 * words * std::mem::size_of::<u32>());
    let convergence_downloaded = downloads[0];
    assert_eq!(
            convergence_downloaded,
            4,
            "parallel IFDS batch convergence must download one final changed flag total, not synchronize per iteration or per query"
        );
    assert!(
        !dispatch
            .uploaded_lengths
            .borrow()
            .iter()
            .any(|&len| len == 4),
        "parallel IFDS batch convergence must not upload a 4-byte changed reset every iteration"
    );
}

#[test]
fn resident_parallel_batch_seed_setup_does_not_upload_frontier_matrix() {
    let dispatch = FakeResidentDispatch::new_with_program_graph_buffers(1);
    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 seed_a = [(0, 0, 0)];
    let seed_b = [(1, 0, 0)];
    let seed_c = [(2, 0, 0)];
    let seed_d = [(3, 0, 0)];
    let seed_e = [(4, 0, 0)];
    let _ = solve_resident_prepared_many_parallel_via(
        &dispatch,
        &prepared,
        &[
            &seed_a[..],
            &seed_b[..],
            &seed_c[..],
            &seed_d[..],
            &seed_e[..],
        ],
        8,
    )
    .expect("fake resident parallel batch solver should converge");
    let full_frontier_matrix_bytes = 5 * words * std::mem::size_of::<u32>();
    assert!(
            !dispatch
                .uploaded_lengths
                .borrow()
                .iter()
                .any(|&len| len == full_frontier_matrix_bytes),
            "parallel IFDS batch seed setup must scatter compact seeds on GPU instead of uploading the full frontier matrix"
        );
}

#[test]
fn resident_parallel_scratch_can_be_reused_across_batches() {
    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_parallel_scratch(&dispatch, &prepared, 3)
        .expect("parallel scratch allocation should succeed");
    let allocations_after_scratch = dispatch.allocate_calls.get();
    assert_eq!(
            dispatch.buffers.borrow().len(),
            9,
            "parallel scratch allocation should create frontier, changed flag, and reusable seed staging buffers"
        );
    let seed_a = [(0, 0, 0)];
    let seed_b = [(0, 1, 0)];
    let seed_c = [(0, 2, 0)];
    let first = solve_resident_prepared_many_parallel_with_scratch_via(
        &dispatch,
        &prepared,
        &scratch,
        &[&seed_a[..], &seed_b[..]],
        8,
    )
    .expect("first parallel scratch solve should converge");
    let second = solve_resident_prepared_many_parallel_with_scratch_via(
        &dispatch,
        &prepared,
        &scratch,
        &[&seed_c[..]],
        8,
    )
    .expect("second parallel scratch solve should converge");
    assert_eq!(first, vec![vec![0], vec![1024]]);
    assert_eq!(second, vec![vec![2048]]);
    assert_eq!(
        dispatch.allocate_calls.get(),
        allocations_after_scratch,
        "reusing parallel scratch must not allocate resident buffers during solve"
    );
    assert_eq!(
        dispatch.buffers.borrow().len(),
        9,
        "reusing parallel scratch across batches must not allocate more resident buffers"
    );
    free_resident_ifds_parallel_scratch(&dispatch, scratch)
        .expect("parallel scratch free should succeed");
}

#[test]
fn resident_parallel_scratch_clears_stale_changed_history_on_gpu() {
    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_parallel_scratch_with_iterations(&dispatch, &prepared, 2, 8)
            .expect("parallel scratch allocation should succeed");
    dispatch.buffers.borrow_mut()[scratch.changed] = vec![0xFF; scratch.changed_byte_len];
    let seed = [(0, 0, 0)];
    let got = solve_resident_prepared_many_parallel_with_scratch_via(
        &dispatch,
        &prepared,
        &scratch,
        &[&seed[..]],
        8,
    )
    .expect("stale changed history must be cleared before solve");
    assert_eq!(got, vec![vec![0]]);
    assert!(
        !dispatch
            .uploaded_lengths
            .borrow()
            .iter()
            .any(|&len| len == 4),
        "changed-flag clearing must stay GPU-resident, not a 4-byte host reset upload"
    );
    free_resident_ifds_parallel_scratch(&dispatch, scratch)
        .expect("parallel scratch free should succeed");
}