weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
use super::*;
use crate::{
    live::live_closure_borrowed_into_with_scratch_via,
    reaching::reaching_closure_borrowed_into_with_scratch_via,
};

#[test]
fn fixed_point_scratch_reuses_output_slot_across_analysis_families() {
    let mut scratch = FixedPointScratch::default();
    let second_analysis_saw_existing_output_slot = std::cell::Cell::new(false);
    let phase = std::cell::Cell::new(0_u32);
    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);
        }
        if phase.get() == 1 && outputs.len() == 1 {
            second_analysis_saw_existing_output_slot.set(true);
        }
        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 reaching = reaching_closure_borrowed_into_with_scratch_via(
        &dispatch,
        2,
        &[0, 1, 1],
        &[1],
        &[vyre_primitives::predicate::edge_kind::CONTROL],
        &[0b01],
        4,
        &mut scratch,
    )
    .expect("reaching closure must converge with reusable scratch");
    assert_eq!(reaching, vec![0b11]);
    assert_eq!(scratch.output_slot_count(), 1);
    assert!(scratch.frontier_byte_capacity() >= 4);
    assert!(scratch.next_word_capacity() >= 1);

    phase.set(1);
    let live = live_closure_borrowed_into_with_scratch_via(
        &dispatch,
        2,
        &[0, 1, 1],
        &[1],
        &[vyre_primitives::predicate::edge_kind::CONTROL],
        &[0b10],
        4,
        &mut scratch,
    )
    .expect("live closure must converge with reusable scratch");
    assert_eq!(live, vec![0b11]);
    assert!(second_analysis_saw_existing_output_slot.get());
}

#[test]
fn fixed_point_scratch_with_capacities_preallocates_hot_buffers() {
    let scratch = FixedPointScratch::with_capacities(16, 3, 5)
        .expect("hot scratch capacity request must fit");

    assert_eq!(scratch.output_slot_count(), 3);
    assert!(scratch.output_slot_capacity() >= 3);
    assert!(scratch.frontier_word_capacity() >= 16);
    assert!(scratch.frontier_byte_capacity() >= 64);
    assert!(scratch.next_word_capacity() >= 16);
    assert!(scratch.resource_capacity() >= 5);
}

#[test]
fn fixed_point_scratch_with_capacities_rejects_frontier_byte_overflow() {
    let error = FixedPointScratch::with_capacities(usize::MAX, 0, 0)
        .expect_err("overflowing frontier scratch request must return an error");

    assert!(
        error.contains("frontier byte capacity overflowed usize")
            && error.contains("Fix: lower frontier_words"),
        "scratch capacity overflow must name the failed capacity and operator action"
    );
}

#[test]
fn fixed_point_scratch_clear_preserves_preallocated_capacities() {
    let mut scratch =
        FixedPointScratch::with_capacities(8, 2, 4).expect("hot scratch capacity request must fit");
    scratch.frontier_words.extend_from_slice(&[1, 2, 3]);
    scratch.frontier_bytes.extend_from_slice(&[1, 0, 0, 0]);
    scratch.next.extend_from_slice(&[4, 5]);
    scratch.outputs[0].extend_from_slice(&[9, 9, 9]);
    scratch.outputs[1].extend_from_slice(&[8, 8]);
    let first_output_ptr = scratch.outputs[0].as_ptr() as usize;
    let second_output_ptr = scratch.outputs[1].as_ptr() as usize;
    let output_capacity = scratch.output_slot_capacity();
    let frontier_word_capacity = scratch.frontier_word_capacity();
    let frontier_byte_capacity = scratch.frontier_byte_capacity();
    let next_word_capacity = scratch.next_word_capacity();
    let resource_capacity = scratch.resource_capacity();

    scratch.clear();

    assert_eq!(scratch.output_slot_count(), 2);
    assert!(scratch.outputs.iter().all(Vec::is_empty));
    assert_eq!(scratch.outputs[0].as_ptr() as usize, first_output_ptr);
    assert_eq!(scratch.outputs[1].as_ptr() as usize, second_output_ptr);
    assert!(scratch.output_slot_capacity() >= output_capacity);
    assert!(scratch.frontier_word_capacity() >= frontier_word_capacity);
    assert!(scratch.frontier_byte_capacity() >= frontier_byte_capacity);
    assert!(scratch.next_word_capacity() >= next_word_capacity);
    assert!(scratch.resource_capacity() >= resource_capacity);
}

#[test]
fn fixed_point_scratch_reuses_filtered_edge_kind_mask_buffer() {
    let mut scratch = FixedPointScratch::default();
    scratch
        .prepare_edge_kind_masks(
            &[
                vyre_primitives::predicate::edge_kind::CONTROL,
                vyre_primitives::predicate::edge_kind::DOMINANCE,
                vyre_primitives::predicate::edge_kind::ASSIGNMENT,
            ],
            vyre_primitives::predicate::edge_kind::CONTROL
                | vyre_primitives::predicate::edge_kind::ASSIGNMENT,
        )
        .unwrap();
    let capacity = scratch.edge_kind_mask_capacity();
    let ptr = scratch.edge_kind_masks.as_ptr() as usize;

    assert_eq!(
        scratch.edge_kind_masks,
        vec![
            vyre_primitives::predicate::edge_kind::CONTROL,
            0,
            vyre_primitives::predicate::edge_kind::ASSIGNMENT,
        ]
    );

    scratch
        .prepare_edge_kind_masks(
            &[vyre_primitives::predicate::edge_kind::ASSIGNMENT],
            vyre_primitives::predicate::edge_kind::ASSIGNMENT,
        )
        .unwrap();

    assert_eq!(
        scratch.edge_kind_masks,
        vec![vyre_primitives::predicate::edge_kind::ASSIGNMENT]
    );
    assert_eq!(scratch.edge_kind_masks.as_ptr() as usize, ptr);
    assert!(scratch.edge_kind_mask_capacity() >= capacity);
}

#[test]
fn fixed_point_scratch_reuses_reverse_csr_buffers() {
    let mut scratch = FixedPointScratch::default();
    scratch.prepare_reverse_csr_scratch(4, 8).unwrap();
    let offsets_capacity = scratch.reverse_offset_capacity();
    let targets_capacity = scratch.reverse_target_capacity();

    scratch.prepare_reverse_csr_scratch(2, 3).unwrap();

    assert_eq!(scratch.reverse_offsets.len(), 3);
    assert_eq!(scratch.reverse_targets.len(), 0);
    assert!(scratch.reverse_offset_capacity() >= offsets_capacity);
    assert!(scratch.reverse_target_capacity() >= targets_capacity);
}

#[test]
fn fixed_point_scratch_replaces_outputs_without_dropping_slots() {
    let mut scratch = FixedPointScratch {
        frontier_words: Vec::new(),
        frontier_bytes: Vec::new(),
        previous_frontier_bytes: Vec::new(),
        changed_bytes: Vec::new(),
        static_input_bytes: Vec::new(),
        outputs: vec![Vec::with_capacity(8), Vec::with_capacity(4)],
        next: Vec::new(),
        resources: Vec::new(),
        alternate_resources: Vec::new(),
        changed_resources: Vec::new(),
        clear_changed_program: None,
        forward_changed_program: None,
        edge_kind_masks: Vec::new(),
        reverse_indegree: Vec::new(),
        reverse_cursor: Vec::new(),
        reverse_offsets: Vec::new(),
        reverse_targets: Vec::new(),
        reverse_masks: Vec::new(),
        frontier_density: FrontierDensityTelemetry::default(),
        dispatch_config: vyre::DispatchConfig::default(),
        #[cfg(feature = "gpu-telemetry")]
        telemetry_popcount_input_bytes: Vec::new(),
        #[cfg(feature = "gpu-telemetry")]
        telemetry_popcount_output_bytes: Vec::new(),
        #[cfg(feature = "gpu-telemetry")]
        telemetry_sum_bytes: Vec::new(),
        #[cfg(feature = "gpu-telemetry")]
        telemetry_xor_words: Vec::new(),
        #[cfg(feature = "gpu-telemetry")]
        telemetry_popcount_program: None,
        #[cfg(feature = "gpu-telemetry")]
        telemetry_reduce_program: None,
    };
    let outputs_addr = scratch.outputs.as_ptr() as usize;
    let first_slot_addr = scratch.outputs[0].as_ptr() as usize;
    let second_slot_addr = scratch.outputs[1].as_ptr() as usize;

    scratch.replace_outputs_preserving_slots(vec![vec![1, 2, 3], vec![4]]);

    assert_eq!(scratch.outputs, vec![vec![1, 2, 3], vec![4]]);
    assert_eq!(scratch.outputs.as_ptr() as usize, outputs_addr);
    assert_eq!(scratch.outputs[0].as_ptr() as usize, first_slot_addr);
    assert_eq!(scratch.outputs[1].as_ptr() as usize, second_slot_addr);
}

#[test]
fn fixed_point_scratch_records_frontier_density_without_allocation() {
    let mut scratch = FixedPointScratch::default();
    scratch.prepare_frontier_words(1, &[0b0011]).unwrap();
    scratch.begin_frontier_density(5);
    scratch.prepare_next_words(1).unwrap();
    scratch.next.extend_from_slice(&[0b1011]);

    scratch.record_decoded_frontier_transition();

    let telemetry = scratch.frontier_density();
    assert_eq!(telemetry.domain_bits, 5);
    assert_eq!(telemetry.samples, 2);
    assert_eq!(telemetry.iterations, 1);
    assert_eq!(telemetry.active_bits_total, 5);
    assert_eq!(telemetry.delta_bits_total, 1);
    assert_eq!(telemetry.last_active_bits, 3);
    assert_eq!(telemetry.last_delta_bits, 1);
    assert_eq!(telemetry.peak_active_bits, 3);
    assert_eq!(telemetry.peak_delta_bits, 1);
    assert_eq!(telemetry.last_active_density_ppm(), 600_000);
    assert_eq!(telemetry.last_delta_density_ppm(), 200_000);
    assert_eq!(
        telemetry.recommended_execution_mode(),
        FrontierExecutionMode::Dense
    );
}

#[test]
fn frontier_density_recommends_sparse_for_low_density_delta() {
    let mut telemetry = FrontierDensityTelemetry {
        domain_bits: 128,
        samples: 2,
        iterations: 1,
        active_bits_total: 2,
        delta_bits_total: 1,
        last_active_bits: 1,
        last_delta_bits: 1,
        peak_active_bits: 1,
        peak_delta_bits: 1,
        truncated_frontier_samples: 0,
        domain_overflow_events: 0,
        arithmetic_overflow_events: 0,
    };
    assert_eq!(
        telemetry.recommended_execution_mode(),
        FrontierExecutionMode::Sparse
    );

    telemetry.active_bits_total = 64;
    telemetry.last_active_bits = 32;
    telemetry.delta_bits_total = 12;
    telemetry.peak_active_bits = 32;
    assert_eq!(
        telemetry.recommended_execution_mode(),
        FrontierExecutionMode::Hybrid
    );
}

#[test]
fn fixed_point_scratch_reuses_grid_dispatch_config_storage() {
    let mut scratch = FixedPointScratch::default();
    let mut base = vyre::DispatchConfig::default();
    base.profile = Some("steady-state".to_string());
    base.label = Some("frontier-loop".to_string());
    base.max_output_bytes = Some(4096);

    scratch.prepare_grid_dispatch_config(&base, [7, 1, 1]);
    let profile_ptr = scratch
        .dispatch_config
        .profile
        .as_ref()
        .expect("profile should be cached")
        .as_ptr() as usize;
    let label_ptr = scratch
        .dispatch_config
        .label
        .as_ref()
        .expect("label should be cached")
        .as_ptr() as usize;

    scratch.prepare_grid_dispatch_config(&base, [11, 1, 1]);

    assert_eq!(scratch.dispatch_config.grid_override, Some([11, 1, 1]));
    assert_eq!(
        scratch
            .dispatch_config
            .profile
            .as_ref()
            .map(|value| value.as_ptr() as usize),
        Some(profile_ptr)
    );
    assert_eq!(
        scratch
            .dispatch_config
            .label
            .as_ref()
            .map(|value| value.as_ptr() as usize),
        Some(label_ptr)
    );
    assert_eq!(scratch.dispatch_config.max_output_bytes, Some(4096));
}