weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
//! Parity test: GPU points-to subset closure matches the reference oracle.

#![cfg(test)]

use vyre::DispatchConfig;
use vyre::VyreBackend;
use vyre_driver_cuda::{CudaBackend, CudaBackendRegistration};
use vyre_primitives::predicate::edge_kind;
use weir::fixed_point_resident::{
    FixedPointResidentFrontierScratch, FixedPointResidentGraph, FixedPointResidentPlan,
};
use weir::oracle::graph::points_to_subset_closure as reference_subset_closure;
use weir::oracle::graph::points_to_subset_closure_borrowed_via as subset_closure_via;
use weir::points_to::prepare_points_to_subset_plan;

fn run_dispatch(
    backend: &CudaBackend,
) -> impl Fn(&vyre::ir::Program, &[&[u8]], Option<[u32; 3]>) -> Result<Vec<Vec<u8>>, String> + '_ {
    move |program, inputs, grid_override| {
        let mut config = DispatchConfig::default();
        config.grid_override = grid_override;
        backend
            .dispatch_borrowed(program, inputs, &config)
            .map_err(|err| err.to_string())
    }
}

fn live_dispatcher() -> CudaBackend {
    CudaBackend::acquire()
        .expect("CudaBackend::acquire failed on a host that must have an NVIDIA GPU.")
}

#[test]
fn cuda_points_to_subset_closure_chain() {
    let backend = live_dispatcher();
    let dispatch = run_dispatch(&backend);
    // Chain 0 -> 1 -> 2 -> 3 with ASSIGNMENT kind.
    let edge_offsets = vec![0u32, 1, 2, 3, 3];
    let edge_targets = vec![1u32, 2, 3];
    let edge_kind_mask = vec![edge_kind::ASSIGNMENT; 3];
    let seed_bits = vec![0b0001u32];
    let reference =
        reference_subset_closure(4, &edge_offsets, &edge_targets, &edge_kind_mask, &seed_bits);
    let gpu = subset_closure_via(
        &dispatch,
        4,
        &edge_offsets,
        &edge_targets,
        &edge_kind_mask,
        &seed_bits,
        16,
    )
    .expect("dispatch");
    assert_eq!(gpu, reference, "chain subset closure divergence");
    assert_eq!(gpu, vec![0b1111u32]);
}

#[test]
fn cuda_points_to_diamond() {
    let backend = live_dispatcher();
    let dispatch = run_dispatch(&backend);
    // Diamond 0 -> {1, 2} -> 3, MEM_LOAD kind.
    let edge_offsets = vec![0u32, 2, 3, 4, 4];
    let edge_targets = vec![1u32, 2, 3, 3];
    let edge_kind_mask = vec![edge_kind::MEM_LOAD; 4];
    let seed_bits = vec![0b0001u32];
    let reference =
        reference_subset_closure(4, &edge_offsets, &edge_targets, &edge_kind_mask, &seed_bits);
    let gpu = subset_closure_via(
        &dispatch,
        4,
        &edge_offsets,
        &edge_targets,
        &edge_kind_mask,
        &seed_bits,
        16,
    )
    .expect("dispatch");
    assert_eq!(gpu, reference);
    assert_eq!(gpu, vec![0b1111u32]);
}

#[test]
fn cuda_points_to_filtered_kind_mask_blocks_propagation() {
    let backend = live_dispatcher();
    let dispatch = run_dispatch(&backend);
    // Edge with kind NOT in POINTS_TO_EDGE_MASK  -  closure should not propagate.
    // Use CONTROL kind, which is not in the points-to mask.
    let edge_offsets = vec![0u32, 1, 1];
    let edge_targets = vec![1u32];
    let edge_kind_mask = vec![edge_kind::CONTROL];
    let seed_bits = vec![0b01u32];
    let reference =
        reference_subset_closure(2, &edge_offsets, &edge_targets, &edge_kind_mask, &seed_bits);
    let gpu = subset_closure_via(
        &dispatch,
        2,
        &edge_offsets,
        &edge_targets,
        &edge_kind_mask,
        &seed_bits,
        16,
    )
    .expect("dispatch");
    assert_eq!(gpu, reference);
    assert_eq!(gpu, vec![0b01u32]);
}

#[test]
fn cuda_points_to_self_loop_terminates() {
    let backend = live_dispatcher();
    let dispatch = run_dispatch(&backend);
    let edge_offsets = vec![0u32, 1, 1];
    let edge_targets = vec![0u32];
    let edge_kind_mask = vec![edge_kind::ALIAS];
    let seed_bits = vec![0b01u32];
    let reference =
        reference_subset_closure(2, &edge_offsets, &edge_targets, &edge_kind_mask, &seed_bits);
    let gpu = subset_closure_via(
        &dispatch,
        2,
        &edge_offsets,
        &edge_targets,
        &edge_kind_mask,
        &seed_bits,
        50,
    )
    .expect("dispatch");
    assert_eq!(gpu, reference);
}

#[test]
fn cuda_points_to_multiple_edge_kinds_all_in_mask() {
    let backend = live_dispatcher();
    let dispatch = run_dispatch(&backend);
    // Mix of allowed kinds: ASSIGNMENT, MEM_STORE, MUT_REF, PHI.
    let edge_offsets = vec![0u32, 1, 2, 3, 4, 4];
    let edge_targets = vec![1u32, 2, 3, 4];
    let edge_kind_mask = vec![
        edge_kind::ASSIGNMENT,
        edge_kind::MEM_STORE,
        edge_kind::MUT_REF,
        edge_kind::PHI,
    ];
    let seed_bits = vec![0b00001u32];
    let reference =
        reference_subset_closure(5, &edge_offsets, &edge_targets, &edge_kind_mask, &seed_bits);
    let gpu = subset_closure_via(
        &dispatch,
        5,
        &edge_offsets,
        &edge_targets,
        &edge_kind_mask,
        &seed_bits,
        16,
    )
    .expect("dispatch");
    assert_eq!(gpu, reference);
    assert_eq!(gpu, vec![0b11111u32]);
}

#[test]
fn cuda_points_to_resident_graph_parity() {
    let backend = CudaBackendRegistration::new(live_dispatcher());
    let edge_offsets = vec![0u32, 2, 3, 4, 4];
    let edge_targets = vec![1u32, 2, 3, 3];
    let edge_kind_mask = vec![edge_kind::MEM_LOAD; 4];
    let seed_bits = vec![0b0001u32];
    let reference =
        reference_subset_closure(4, &edge_offsets, &edge_targets, &edge_kind_mask, &seed_bits);
    let plan = prepare_points_to_subset_plan(4, &edge_offsets, &edge_targets, &edge_kind_mask)
        .expect("points-to plan must prepare for resident CUDA parity");
    let config = DispatchConfig::default();
    let pipeline = backend
        .compile_native(plan.program(), &config)
        .expect("CUDA native compile must succeed for resident points-to parity")
        .expect("CUDA backend registration must return a native compiled pipeline for resident points-to parity");
    let resident = FixedPointResidentGraph::upload(&backend, plan.graph())
        .expect("CUDA backend must upload points-to graph as resident resources");
    let mut scratch = weir::fixed_point_scratch::FixedPointScratch::default();

    let gpu = resident
        .points_to_subset_resident_frontier(
            &backend,
            &*pipeline,
            &seed_bits,
            16,
            &config,
            &mut scratch,
        )
        .expect("resident CUDA points-to dispatch must converge");

    assert_eq!(gpu, reference);
    assert_eq!(gpu, vec![0b1111u32]);
    resident
        .free(&backend)
        .expect("resident points-to graph resources must free cleanly");
}

#[test]
fn cuda_points_to_resident_frontier_reuse_parity() {
    let backend = CudaBackendRegistration::new(live_dispatcher());
    let edge_offsets = vec![0u32, 2, 3, 4, 4];
    let edge_targets = vec![1u32, 2, 3, 3];
    let edge_kind_mask = vec![edge_kind::MEM_LOAD; 4];
    let first_seed = vec![0b0001u32];
    let second_seed = vec![0b0010u32];
    let first_reference = reference_subset_closure(
        4,
        &edge_offsets,
        &edge_targets,
        &edge_kind_mask,
        &first_seed,
    );
    let second_reference = reference_subset_closure(
        4,
        &edge_offsets,
        &edge_targets,
        &edge_kind_mask,
        &second_seed,
    );
    let plan = prepare_points_to_subset_plan(4, &edge_offsets, &edge_targets, &edge_kind_mask)
        .expect("points-to plan must prepare for resident CUDA frontier reuse parity");
    let config = DispatchConfig::default();
    let pipeline = backend
        .compile_native(plan.program(), &config)
        .expect("CUDA native compile must succeed for resident points-to frontier reuse parity")
        .expect("CUDA backend registration must return a native compiled pipeline for resident points-to frontier reuse parity");
    let resident_plan = FixedPointResidentPlan::new(&backend, plan.graph(), pipeline)
        .expect("CUDA backend must upload points-to resident plan resources");
    let mut scratch = weir::fixed_point_scratch::FixedPointScratch::default();
    let mut resident_frontier = FixedPointResidentFrontierScratch::default();

    let first_gpu = resident_plan
        .points_to_subset_reusing_frontier(
            &backend,
            &first_seed,
            16,
            &config,
            &mut scratch,
            &mut resident_frontier,
        )
        .expect("first resident CUDA points-to frontier reuse run must converge");
    let second_gpu = resident_plan
        .points_to_subset_reusing_frontier(
            &backend,
            &second_seed,
            16,
            &config,
            &mut scratch,
            &mut resident_frontier,
        )
        .expect("second resident CUDA points-to frontier reuse run must converge");

    assert!(resident_frontier.is_allocated());
    assert_eq!(resident_frontier.byte_len(), 4);
    assert_eq!(first_gpu, first_reference);
    assert_eq!(second_gpu, second_reference);
    assert_eq!(first_gpu, vec![0b1111u32]);
    assert_eq!(second_gpu, vec![0b1010u32]);
    resident_plan
        .free_with_frontier(&backend, &mut resident_frontier)
        .expect("resident points-to plan and frontier resources must free cleanly");
}

#[test]
fn cuda_points_to_resident_sequence_window_uses_final_convergence_readback_only() {
    let backend = CudaBackendRegistration::new(live_dispatcher());
    let edge_offsets = vec![0u32, 1, 2, 3, 4, 4];
    let edge_targets = vec![1u32, 2, 3, 4];
    let edge_kind_mask = vec![edge_kind::MEM_LOAD; 4];
    let seed_bits = vec![0b00001u32];
    let reference =
        reference_subset_closure(5, &edge_offsets, &edge_targets, &edge_kind_mask, &seed_bits);
    let plan = prepare_points_to_subset_plan(5, &edge_offsets, &edge_targets, &edge_kind_mask)
        .expect("points-to plan must prepare for fixed-window resident CUDA parity");
    let resident = FixedPointResidentGraph::upload(&backend, plan.graph())
        .expect("CUDA backend must upload fixed-window points-to graph resources");
    let mut scratch = weir::fixed_point_scratch::FixedPointScratch::default();
    let mut resident_frontier = FixedPointResidentFrontierScratch::default();

    backend.reset_telemetry();
    let gpu = weir::points_to::subset_closure_resident_plan_with_reusable_frontier_scratch_sequence_window(
            &backend,
            plan.program(),
            &resident,
            &seed_bits,
            16,
            &mut scratch,
            &mut resident_frontier,
        )
        .expect("fixed-window resident CUDA points-to dispatch must converge");
    let telemetry = backend.telemetry_snapshot();

    assert_eq!(gpu, reference);
    assert_eq!(gpu, vec![0b11111u32]);
    assert_eq!(
        telemetry.readback_bytes, 8,
        "Fix: fixed-window points-to resident solve must read only final and previous one-word frontiers, not poll every iteration."
    );
    assert!(
        telemetry.sync_points < 16,
        "Fix: fixed-window points-to resident solve must not synchronize once per fixed-point iteration; observed {} sync points.",
        telemetry.sync_points
    );
    assert!(
        telemetry.param_upload_bytes < telemetry.kernel_launches.saturating_mul(4),
        "Fix: sequence-window points-to resident solve must hoist repeated fixed-point launch parameter uploads; observed {} parameter bytes across {} launches.",
        telemetry.param_upload_bytes,
        telemetry.kernel_launches
    );
    resident_frontier
        .clear(&backend)
        .expect("fixed-window resident points-to frontier resources must free cleanly");
    resident
        .free(&backend)
        .expect("fixed-window resident points-to graph resources must free cleanly");
}