weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
#![allow(deprecated)]

use super::*;

fn assert_dominates(dom: &[u32], words: usize, dominator: u32, dominated: u32) {
    let row = &dom[dominated as usize * words..(dominated as usize + 1) * words];
    let bit = dominator as usize;
    assert!(
        row[bit / 32] & (1u32 << (bit % 32)) != 0,
        "expected {dominator} to dominate {dominated}"
    );
}

fn assert_not_dominates(dom: &[u32], words: usize, dominator: u32, dominated: u32) {
    let row = &dom[dominated as usize * words..(dominated as usize + 1) * words];
    let bit = dominator as usize;
    assert!(
        row[bit / 32] & (1u32 << (bit % 32)) == 0,
        "expected {dominator} to NOT dominate {dominated}"
    );
}

#[test]
fn straight_line_chain() {
    let succ = vec![vec![1], vec![2], vec![3], vec![]];
    let dom = compute_cpu(4, &succ, 0);
    let words = crate::graph_layout::LinearDomain::new(4).bitset_words() as usize;
    for n in 0..=3 {
        assert_dominates(&dom, words, 0, n);
    }
    assert_dominates(&dom, words, 1, 1);
    assert_dominates(&dom, words, 1, 2);
    assert_dominates(&dom, words, 1, 3);
    assert_dominates(&dom, words, 2, 2);
    assert_dominates(&dom, words, 2, 3);
    assert_dominates(&dom, words, 3, 3);
}

#[test]
fn diamond_does_not_make_branch_dominate() {
    let succ = vec![vec![1, 2], vec![3], vec![3], vec![]];
    let dom = compute_cpu(4, &succ, 0);
    let words = crate::graph_layout::LinearDomain::new(4).bitset_words() as usize;
    for n in 0..=3 {
        assert_dominates(&dom, words, 0, n);
    }
    let row3 = &dom[3 * words..4 * words];
    assert!(
        row3[0] & (1u32 << 1) == 0,
        "1 must NOT dominate 3 in a diamond"
    );
    assert!(
        row3[0] & (1u32 << 2) == 0,
        "2 must NOT dominate 3 in a diamond"
    );
}

#[test]
fn unreachable_node_keeps_entry_only() {
    let succ = vec![vec![], vec![]];
    let dom = compute_cpu(2, &succ, 0);
    let words = crate::graph_layout::LinearDomain::new(2).bitset_words() as usize;
    assert_dominates(&dom, words, 0, 0);
    assert_dominates(&dom, words, 0, 1);
    assert_not_dominates(&dom, words, 1, 0);
    assert_not_dominates(&dom, words, 1, 1);
}

#[test]
fn malformed_cfg_shape_panics_in_cpu_oracle() {
    let short_successor_table = std::panic::catch_unwind(|| {
        let _ = compute_cpu(2, &[vec![1]], 0);
    });
    assert!(
        short_successor_table.is_err(),
        "successor table length must match node_count instead of silently treating missing rows as empty"
    );

    let invalid_entry = std::panic::catch_unwind(|| {
        let _ = compute_cpu(2, &[vec![1], vec![]], 7);
    });
    assert!(
        invalid_entry.is_err(),
        "invalid entry must fail loudly instead of returning an all-zero dominator matrix"
    );

    let invalid_edge = std::panic::catch_unwind(|| {
        let _ = compute_cpu(2, &[vec![2], vec![]], 0);
    });
    assert!(
        invalid_edge.is_err(),
        "out-of-range CFG edge must fail loudly instead of being dropped"
    );
}

#[test]
fn compute_via_rejects_out_of_domain_output_tail_bits() {
    let dispatch = |_: &vyre::ir::Program, _: &[Vec<u8>], _: Option<[u32; 3]>| {
        Ok(vec![vyre_primitives::wire::pack_u32_slice(&[0u32, 0b100])])
    };
    let err = compute_via(&dispatch, 2, &[vec![1], vec![]], 0, 1)
        .expect_err("dominator matrix output tail bit outside node_count must be rejected");
    assert!(
        err.contains("outside the declared domain"),
        "unexpected diagnostic: {err}"
    );
}

#[test]
fn compute_borrowed_into_reuses_matrix_output_slot() {
    use std::cell::Cell;

    let node_count = 3u32;
    let words = crate::graph_layout::LinearDomain::new(node_count).bitset_words() as usize;
    let mut outputs = vec![Vec::with_capacity(node_count as usize * words * 4)];
    let outputs_addr = outputs.as_ptr() as usize;
    let slot_addr = outputs[0].as_ptr() as usize;
    let calls = Cell::new(0usize);

    let out = compute_borrowed_into_via(
        &|_, inputs, grid, outputs| {
            assert_eq!(grid, Some([3, 1, 1]));
            assert_eq!(inputs.len(), 4);
            assert_eq!(outputs.len(), 1);
            assert_eq!(outputs.as_ptr() as usize, outputs_addr);
            assert_eq!(outputs[0].as_ptr() as usize, slot_addr);

            let pred_offsets = unpack_words(inputs[0]);
            let pred_targets = unpack_words(inputs[1]);
            let current = unpack_words(inputs[2]);
            assert_eq!(pred_offsets, vec![0, 0, 1, 3]);
            assert_eq!(pred_targets, vec![0, 0, 1]);
            assert_eq!(current.len(), 3);

            let mut next = vec![0u32; 3];
            for node in 0..3usize {
                let start = pred_offsets[node] as usize;
                let end = pred_offsets[node + 1] as usize;
                next[node] = if node == 0 {
                    0b001
                } else if start == end {
                    0b001
                } else {
                    let mut acc = 0b111;
                    for &pred in &pred_targets[start..end] {
                        acc &= current[pred as usize];
                    }
                    acc | (1u32 << node)
                };
            }

            outputs[0].clear();
            for word in next {
                outputs[0].extend_from_slice(&word.to_le_bytes());
            }
            assert_eq!(outputs[0].as_ptr() as usize, slot_addr);
            calls.set(calls.get() + 1);
            Ok(())
        },
        node_count,
        &[vec![1, 2], vec![2], vec![]],
        0,
        8,
        &mut outputs,
    )
    .expect("caller-owned matrix output scratch should decode");

    assert_eq!(calls.get(), 2);
    assert_eq!(out, compute_cpu(3, &[vec![1, 2], vec![2], vec![]], 0));
    assert_eq!(outputs.len(), 1);
    assert_eq!(outputs.as_ptr() as usize, outputs_addr);
    assert_eq!(outputs[0].as_ptr() as usize, slot_addr);
}

fn unpack_words(bytes: &[u8]) -> Vec<u32> {
    vyre_primitives::wire::decode_u32_le_bytes_all(bytes)
}