weirflow 0.1.0

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

#[test]
fn fixed_point_batch_reuses_prepared_reaching_plan() {
    let dispatch = |_: &vyre::ir::Program,
                    inputs: &[&[u8]],
                    _: Option<[u32; 3]>,
                    outputs: &mut Vec<Vec<u8>>| {
        if inputs.len() < 6 {
            return crate::fixed_point_closure::fallback_bitset_equal_dispatch(inputs, outputs);
        }
        let frontier = u32::from_le_bytes(inputs[5][..4].try_into().unwrap());
        let next = frontier | 0b11;
        if outputs.is_empty() {
            outputs.push(Vec::new());
        }
        outputs[0].clear();
        outputs[0].extend_from_slice(&next.to_le_bytes());
        Ok(())
    };
    let graph = CsrGraph::new(
        2,
        &[0, 1, 1],
        &[1],
        &[vyre_primitives::predicate::edge_kind::CONTROL],
    );
    let mut batch = FixedPointBatch::new(&dispatch);
    let plan = batch
        .prepare_reaching_plan(graph)
        .expect("batch must prepare reaching plan once");
    let retained = plan.retained_graph_bytes();
    let mut into = Vec::with_capacity(1);
    let into_ptr = into.as_ptr();

    let first = batch
        .reaching_plan(&plan, &[0b01], 4)
        .expect("first planned reaching run must converge");
    batch
        .reaching_plan_into(&plan, &[0b01], 4, &mut into)
        .expect("planned reaching_into run must converge");
    let second = batch
        .reaching_plan(&plan, &[0b10], 4)
        .expect("second planned reaching run must converge");

    assert_eq!(first, vec![0b11]);
    assert_eq!(into, vec![0b11]);
    assert_eq!(
        into.as_ptr(),
        into_ptr,
        "reaching_plan_into must reuse caller result allocation"
    );
    assert_eq!(second, vec![0b11]);
    assert_eq!(plan.retained_graph_bytes(), retained);
    assert!(!plan.program().entry().is_empty());
}

#[test]
fn fixed_point_batch_repacks_prepared_plan_in_place() {
    let dispatch = |_: &vyre::ir::Program,
                    inputs: &[&[u8]],
                    _: Option<[u32; 3]>,
                    outputs: &mut Vec<Vec<u8>>| {
        if inputs.len() < 6 {
            return crate::fixed_point_closure::fallback_bitset_equal_dispatch(inputs, outputs);
        }
        outputs.clear();
        outputs.push(inputs[5].to_vec());
        Ok(())
    };
    let first_graph = CsrGraph::new(
        4,
        &[0, 2, 3, 3, 3],
        &[1, 2, 3],
        &[
            vyre_primitives::predicate::edge_kind::CONTROL,
            vyre_primitives::predicate::edge_kind::ASSIGNMENT,
            vyre_primitives::predicate::edge_kind::CONTROL,
        ],
    );
    let second_graph = CsrGraph::new(
        4,
        &[0, 1, 1, 2, 2],
        &[2, 3],
        &[
            vyre_primitives::predicate::edge_kind::CONTROL,
            vyre_primitives::predicate::edge_kind::CONTROL,
        ],
    );
    let mut batch = FixedPointBatch::new(&dispatch);
    let mut plan = batch
        .prepare_reaching_plan(first_graph)
        .expect("initial reaching plan must prepare");
    let pg_nodes_ptr = plan.graph().pg_nodes_bytes.as_ptr() as usize;
    let edge_offsets_ptr = plan.graph().edge_offsets_bytes.as_ptr() as usize;
    let edge_targets_ptr = plan.graph().edge_targets_bytes.as_ptr() as usize;
    let edge_kind_ptr = plan.graph().edge_kind_mask_bytes.as_ptr() as usize;
    let pg_tags_ptr = plan.graph().pg_node_tags_bytes.as_ptr() as usize;
    let retained = plan.retained_graph_bytes();
    let mask_scratch_ptr = batch.scratch().edge_kind_masks.as_ptr() as usize;
    let mask_scratch_capacity = batch.scratch().edge_kind_mask_capacity();

    batch
        .prepare_reaching_plan_into(second_graph, &mut plan)
        .expect("batch must repack reaching plan in place");

    assert_eq!(
        plan.kind(),
        crate::fixed_point_graph::FixedPointAnalysisKind::Reaching
    );
    assert_eq!(plan.graph().node_count(), 4);
    assert_eq!(plan.graph().edge_count(), 2);
    assert_eq!(plan.graph().pg_nodes_bytes.as_ptr() as usize, pg_nodes_ptr);
    assert_eq!(
        plan.graph().edge_offsets_bytes.as_ptr() as usize,
        edge_offsets_ptr
    );
    assert_eq!(
        plan.graph().edge_targets_bytes.as_ptr() as usize,
        edge_targets_ptr
    );
    assert_eq!(
        plan.graph().edge_kind_mask_bytes.as_ptr() as usize,
        edge_kind_ptr
    );
    assert_eq!(
        plan.graph().pg_node_tags_bytes.as_ptr() as usize,
        pg_tags_ptr
    );
    assert_eq!(plan.retained_graph_bytes(), retained);
    assert_eq!(
        batch.scratch().edge_kind_masks.as_ptr() as usize,
        mask_scratch_ptr
    );
    assert!(batch.scratch().edge_kind_mask_capacity() >= mask_scratch_capacity);
    assert!(!plan.program().entry().is_empty());
}

#[test]
fn fixed_point_batch_reuses_live_and_points_to_plans() {
    let dispatch = |_: &vyre::ir::Program,
                    inputs: &[&[u8]],
                    _: Option<[u32; 3]>,
                    outputs: &mut Vec<Vec<u8>>| {
        if inputs.len() < 6 {
            return crate::fixed_point_closure::fallback_bitset_equal_dispatch(inputs, outputs);
        }
        let frontier = u32::from_le_bytes(inputs[5][..4].try_into().unwrap());
        let next = frontier | 0b11;
        if outputs.is_empty() {
            outputs.push(Vec::new());
        }
        outputs[0].clear();
        outputs[0].extend_from_slice(&next.to_le_bytes());
        Ok(())
    };
    let control_graph = CsrGraph::new(
        2,
        &[0, 1, 1],
        &[1],
        &[vyre_primitives::predicate::edge_kind::CONTROL],
    );
    let points_to_graph = CsrGraph::new(
        2,
        &[0, 1, 1],
        &[1],
        &[vyre_primitives::predicate::edge_kind::ASSIGNMENT],
    );
    let mut batch = FixedPointBatch::new(&dispatch);
    let live_plan = batch
        .prepare_live_plan(control_graph)
        .expect("batch must prepare live plan once");
    let points_to_plan = batch
        .prepare_points_to_subset_plan(points_to_graph)
        .expect("batch must prepare points-to plan once");
    let mut live_into = Vec::with_capacity(1);
    let live_into_ptr = live_into.as_ptr();
    let mut points_to_into = Vec::with_capacity(1);
    let points_to_into_ptr = points_to_into.as_ptr();

    let live = batch
        .live_plan(&live_plan, &[0b10], 4)
        .expect("planned live run must converge");
    batch
        .live_plan_into(&live_plan, &[0b10], 4, &mut live_into)
        .expect("planned live_into run must converge");
    let points_to = batch
        .points_to_subset_plan(&points_to_plan, &[0b01], 4)
        .expect("planned points-to run must converge");
    batch
        .points_to_subset_plan_into(&points_to_plan, &[0b01], 4, &mut points_to_into)
        .expect("planned points-to_into run must converge");

    assert_eq!(live, vec![0b11]);
    assert_eq!(live_into, vec![0b11]);
    assert_eq!(
        live_into.as_ptr(),
        live_into_ptr,
        "live_plan_into must reuse caller result allocation"
    );
    assert_eq!(points_to, vec![0b11]);
    assert_eq!(points_to_into, vec![0b11]);
    assert_eq!(
        points_to_into.as_ptr(),
        points_to_into_ptr,
        "points_to_subset_plan_into must reuse caller result allocation"
    );
    assert_eq!(
        live_plan.kind(),
        crate::fixed_point_graph::FixedPointAnalysisKind::Live
    );
    assert_eq!(
        points_to_plan.kind(),
        crate::fixed_point_graph::FixedPointAnalysisKind::PointsToSubset
    );
    assert!(!live_plan.program().entry().is_empty());
    assert!(!points_to_plan.program().entry().is_empty());
}

#[test]
fn fixed_point_batch_reuses_slice_plan() {
    let dispatch = |_: &vyre::ir::Program,
                    inputs: &[&[u8]],
                    _: Option<[u32; 3]>,
                    outputs: &mut Vec<Vec<u8>>| {
        if inputs.len() < 6 {
            return crate::fixed_point_closure::fallback_bitset_equal_dispatch(inputs, outputs);
        }
        let frontier = u32::from_le_bytes(inputs[5][..4].try_into().unwrap());
        let next = frontier | 0b11;
        if outputs.is_empty() {
            outputs.push(Vec::new());
        }
        outputs[0].clear();
        outputs[0].extend_from_slice(&next.to_le_bytes());
        Ok(())
    };
    let graph = CsrGraph::new(
        2,
        &[0, 1, 1],
        &[1],
        &[vyre_primitives::predicate::edge_kind::CONTROL],
    );
    let mut batch = FixedPointBatch::new(&dispatch);
    let plan = batch
        .prepare_slice_plan(graph)
        .expect("batch must prepare slice plan once");
    let retained = plan.retained_graph_bytes();

    let first = batch
        .slice_plan(&plan, &[0b10], 4)
        .expect("first planned slice run must converge");
    let second = batch
        .slice_plan(&plan, &[0b01], 4)
        .expect("second planned slice run must converge");

    assert_eq!(first, vec![0b11]);
    assert_eq!(second, vec![0b11]);
    assert_eq!(
        plan.kind(),
        crate::fixed_point_graph::FixedPointAnalysisKind::Slice
    );
    assert_eq!(plan.retained_graph_bytes(), retained);
    assert!(!plan.program().entry().is_empty());

    let mut result = Vec::with_capacity(1);
    let result_ptr = result.as_ptr();
    let result_capacity = result.capacity();
    batch
        .slice_plan_into(&plan, &[0b10], 4, &mut result)
        .expect("planned slice into run must fill caller-owned result");
    assert_eq!(result, vec![0b11]);
    assert_eq!(result.as_ptr(), result_ptr);
    assert_eq!(result.capacity(), result_capacity);
}