weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
//! Bisect: does the recall=37.5% (gid<32 fires, gid>=32 doesn't) bug
//! in surgec's stack_overflow_gets rule live in (a) the rule lowering /
//! fusion pass or (b) the vyre-driver-cuda codegen for the
//! `result[gid] = anchor[gid>>5] & (1<<(gid&31)) != 0` pattern?
//!
//! This test builds the minimal Program the rule wrapper emits  -  no
//! resolve_family, no bitset_any, no fusion barriers. Just a per-gid
//! kernel that reads anchor[gid/32], masks bit `1<<(gid%32)`, writes
//! 1 or 0 to result[gid]. We pre-fill `anchor` on the host so there's
//! no cross-arm RAW: if the substrate emits this kernel correctly,
//! every gid whose bit is set in anchor must produce result[gid] = 1.
//!
//! Op id: `weir::tests::df::anchor_bit_codegen`. Soundness: `Exact`.
//! Failure mode: bug is in vyre-driver-cuda codegen for shr / load /
//! store with dynamic indices past the first u32 word boundary. Pass
//! mode: bug is upstream in surgec's lowering or in cross-arm fusion.

use std::sync::Arc;

use vyre::ir::{BufferAccess, BufferDecl, DataType, Expr, Ident, Node, Program};
use vyre::DispatchConfig;
use vyre_driver_cuda::cuda_factory;

/// Build the wrapper-only Program that mirrors what
/// `surgec::lower::visitor::wrap_with_anchor` emits.
///
/// `anchor` count = node_count.div_ceil(32), `result` count = node_count.
fn anchor_wrapper_program(node_count: u32) -> Program {
    let words = node_count.div_ceil(32);
    let body = vec![
        Node::let_bind("gid", Expr::InvocationId { axis: 0 }),
        Node::let_bind("word_idx", Expr::shr(Expr::var("gid"), Expr::u32(5))),
        Node::let_bind(
            "bit_mask",
            Expr::shl(Expr::u32(1), Expr::bitand(Expr::var("gid"), Expr::u32(31))),
        ),
        Node::let_bind("anchor_word", Expr::load("anchor", Expr::var("word_idx"))),
        Node::let_bind(
            "anchor_bit",
            Expr::bitand(Expr::var("anchor_word"), Expr::var("bit_mask")),
        ),
        Node::store(
            "result",
            Expr::var("gid"),
            Expr::select(
                Expr::ne(Expr::var("anchor_bit"), Expr::u32(0)),
                Expr::u32(1),
                Expr::u32(0),
            ),
        ),
    ];
    Program::wrapped(
        vec![
            BufferDecl::storage("anchor", 0, BufferAccess::ReadOnly, DataType::U32)
                .with_count(words),
            BufferDecl::storage("result", 1, BufferAccess::ReadWrite, DataType::U32)
                .with_count(node_count),
        ],
        [256, 1, 1],
        vec![Node::Region {
            generator: Ident::from("weir::tests::df::anchor_bit_codegen"),
            source_region: None,
            body: Arc::new(vec![Node::if_then(
                Expr::lt(Expr::InvocationId { axis: 0 }, Expr::u32(node_count)),
                body,
            )]),
        }],
    )
}

#[test]
fn anchor_bit_codegen_fires_at_every_node_id() {
    let backend = cuda_factory().unwrap_or_else(|error| {
        panic!(
            "CUDA adapter acquisition failed for anchor_bit codegen probe: {error}. \
             Fix: repair the CUDA driver/runtime probe; Weir GPU codegen tests must not skip."
        )
    });
    const NODE_COUNT: u32 = 65536;
    let words = NODE_COUNT.div_ceil(32);
    let program = anchor_wrapper_program(NODE_COUNT);
    let cfg = DispatchConfig::default();

    let probe_nodes: &[u32] = &[
        0, 1, 24, 30, 31, 32, 33, 39, 42, 52, 70, 100, 1000, 4096, 65535,
    ];
    for &node_id in probe_nodes {
        let mut anchor = vec![0u32; words as usize];
        anchor[(node_id / 32) as usize] |= 1u32 << (node_id % 32);
        let anchor_b: Vec<u8> = vyre_primitives::wire::pack_u32_slice(&anchor);
        let result_b: Vec<u8> = vec![0u8; (NODE_COUNT as usize) * 4];
        let inputs: [&[u8]; 2] = [&anchor_b, &result_b];
        let outputs = backend
            .dispatch_borrowed(&program, &inputs, &cfg)
            .unwrap_or_else(|e| panic!("dispatch failed at node {node_id}: {e}"));
        let result = vyre_primitives::wire::decode_u32_le_bytes_all(&outputs[0]);
        let observed = result[node_id as usize];
        eprintln!(
            "node_id={node_id:>5}  word_idx={}  bit={:#010x}  result[{node_id}]={observed}",
            node_id / 32,
            1u32 << (node_id % 32),
        );
        assert_eq!(
            observed, 1,
            "FAILED at node {node_id}: anchor_word_idx={}, expected result[{node_id}]=1, got {observed}. \
             The substrate's shr+bitand+load+store pattern miscompiled for this gid. \
             Bisects the recall bug to vyre-driver-cuda codegen, not surgec lowering.",
            node_id / 32,
        );
        // Every other gid must be 0 (only the chosen bit was set in anchor).
        for (i, w) in result.iter().enumerate() {
            if i as u32 != node_id {
                assert_eq!(
                    *w, 0,
                    "spurious finding at gid {i} when only node {node_id} should fire (result[{i}]={w})"
                );
            }
        }
    }
}