weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
//! Bisect (continued): two-arm fusion (resolve_family + wrapper) works
//! at every gid. The full surgec rule has THREE arms: arm 0
//! resolve_family, arm 1 bitset_any (gid==0 ONLY), arm 2 wrapper.
//! This test reconstructs that exact shape: arm 0 atomic-or, arm 1
//! gid==0 only reads anchor and writes a scalar, arm 2 reads BOTH
//! the scalar and the anchor at every gid and writes result.
//!
//! Op id: `weir::tests::df::three_arm_fusion`. Soundness: `Exact`.
//! Failure mode bisects the recall bug to the bitset_any-style
//! gid-gated middle arm  -  most likely a barrier-after-divergent-
//! control-flow problem in the fusion or the codegen.

use std::sync::Arc;

use vyre::ir::{BufferAccess, BufferDecl, DataType, Expr, Ident, Node, Program};
use vyre::DispatchConfig;
use vyre_driver::grid_sync::dispatch_with_grid_sync_split;
use vyre_driver_cuda::cuda_factory;
use vyre_foundation::execution_plan::fusion::fuse_programs;

const NODE_COUNT: u32 = 65536;

fn arm0_atomic_or() -> Program {
    let t = Expr::InvocationId { axis: 0 };
    let body = vec![
        Node::let_bind("tag_val", Expr::load("tag", t.clone())),
        Node::if_then(
            Expr::ne(Expr::var("tag_val"), Expr::u32(0)),
            vec![
                Node::let_bind("word_idx", Expr::shr(t.clone(), Expr::u32(5))),
                Node::let_bind(
                    "bit",
                    Expr::shl(Expr::u32(1), Expr::bitand(t.clone(), Expr::u32(31))),
                ),
                Node::let_bind(
                    "_",
                    Expr::atomic_or("gets", Expr::var("word_idx"), Expr::var("bit")),
                ),
            ],
        ),
    ];
    Program::wrapped(
        vec![
            BufferDecl::storage("tag", 0, BufferAccess::ReadOnly, DataType::U32)
                .with_count(NODE_COUNT),
            BufferDecl::storage("gets", 1, BufferAccess::ReadWrite, DataType::U32)
                .with_count(NODE_COUNT.div_ceil(32)),
        ],
        [256, 1, 1],
        vec![Node::Region {
            generator: Ident::from("weir::tests::df::three_arm_fusion::arm0"),
            source_region: None,
            body: Arc::new(vec![Node::if_then(
                Expr::lt(t.clone(), Expr::u32(NODE_COUNT)),
                body,
            )]),
        }],
    )
}

/// Arm 1 mirrors `vyre-primitives::bitset::any`: at invocation_id==0
/// only, scan all words of `gets`, store 1 to `scalar[0]` if any bit set.
fn arm1_bitset_any() -> Program {
    let words = NODE_COUNT.div_ceil(32);
    let body = vec![
        Node::let_bind("acc", Expr::u32(0)),
        Node::let_bind("found", Expr::u32(0)),
        Node::loop_for(
            "w",
            Expr::u32(0),
            Expr::u32(words),
            vec![Node::if_then(
                Expr::eq(Expr::var("found"), Expr::u32(0)),
                vec![
                    Node::assign(
                        "acc",
                        Expr::bitor(Expr::var("acc"), Expr::load("gets", Expr::var("w"))),
                    ),
                    Node::if_then(
                        Expr::ne(Expr::var("acc"), Expr::u32(0)),
                        vec![Node::assign("found", Expr::u32(1))],
                    ),
                ],
            )],
        ),
        Node::store(
            "scalar",
            Expr::u32(0),
            Expr::select(
                Expr::ne(Expr::var("acc"), Expr::u32(0)),
                Expr::u32(1),
                Expr::u32(0),
            ),
        ),
    ];
    Program::wrapped(
        vec![
            BufferDecl::storage("gets", 0, BufferAccess::ReadOnly, DataType::U32).with_count(words),
            BufferDecl::storage("scalar", 1, BufferAccess::ReadWrite, DataType::U32).with_count(1),
        ],
        [1, 1, 1],
        vec![Node::Region {
            generator: Ident::from("weir::tests::df::three_arm_fusion::arm1"),
            source_region: None,
            body: Arc::new(vec![Node::if_then(
                Expr::eq(Expr::InvocationId { axis: 0 }, Expr::u32(0)),
                body,
            )]),
        }],
    )
}

fn arm2_wrapper() -> Program {
    let t = Expr::InvocationId { axis: 0 };
    let body = vec![
        Node::let_bind("gid", t.clone()),
        Node::let_bind("scalar_val", Expr::load("scalar", Expr::u32(0))),
        Node::let_bind(
            "scalar_local",
            Expr::select(
                Expr::ne(Expr::var("scalar_val"), Expr::u32(0)),
                Expr::u32(1),
                Expr::u32(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("gets", 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::and(
                    Expr::ne(Expr::var("anchor_bit"), Expr::u32(0)),
                    Expr::ne(Expr::var("scalar_local"), Expr::u32(0)),
                ),
                Expr::u32(1),
                Expr::u32(0),
            ),
        ),
    ];
    Program::wrapped(
        vec![
            BufferDecl::storage("gets", 0, BufferAccess::ReadOnly, DataType::U32)
                .with_count(NODE_COUNT.div_ceil(32)),
            BufferDecl::storage("scalar", 1, BufferAccess::ReadOnly, DataType::U32).with_count(1),
            BufferDecl::storage("result", 2, BufferAccess::ReadWrite, DataType::U32)
                .with_count(NODE_COUNT),
        ],
        [256, 1, 1],
        vec![Node::Region {
            generator: Ident::from("weir::tests::df::three_arm_fusion::arm2"),
            source_region: None,
            body: Arc::new(vec![Node::if_then(
                Expr::lt(t.clone(), Expr::u32(NODE_COUNT)),
                body,
            )]),
        }],
    )
}

#[test]
fn three_arm_fused_rule_fires_at_every_gid() {
    let backend = cuda_factory().unwrap_or_else(|error| {
        panic!(
            "CUDA adapter acquisition failed for three_arm_fusion probe: {error}. \
             Fix: repair the CUDA driver/runtime probe; Weir GPU fusion tests must not skip."
        )
    });
    let program = fuse_programs(&[arm0_atomic_or(), arm1_bitset_any(), arm2_wrapper()])
        .expect("3-arm fuse cleanly");
    let cfg = DispatchConfig::default();

    // Probe block-boundary boundaries: 256-thread blocks at this
    // workgroup_size means gid 0..255 = block 0, 256..511 = block 1,
    // 512..767 = block 2, etc. If 3-arm fusion's barrier is block-only,
    // gids in higher blocks never see arm 1's gid==0-gated write.
    let probe_nodes: &[u32] = &[
        0, 1, 24, 30, 31, 32, 33, 39, 42, 52, 70, 100, 200, 255, 256, 257, 300, 511, 512, 513, 768,
        1000, 1024, 4096, 65535,
    ];
    for &node_id in probe_nodes {
        let mut tag = vec![0u32; NODE_COUNT as usize];
        tag[node_id as usize] = 1;
        let tag_b: Vec<u8> = vyre_primitives::wire::pack_u32_slice(&tag);
        let gets_b: Vec<u8> = vec![0u8; (NODE_COUNT.div_ceil(32) as usize) * 4];
        let scalar_b: Vec<u8> = vec![0u8; 4];
        let result_b: Vec<u8> = vec![0u8; (NODE_COUNT as usize) * 4];
        let inputs: [&[u8]; 4] = [&tag_b, &gets_b, &scalar_b, &result_b];
        let outputs = dispatch_with_grid_sync_split(backend.as_ref(), &program, &inputs, &cfg)
            .unwrap_or_else(|e| panic!("dispatch failed at node {node_id}: {e}"));

        // Locate buffers by READBACK ORDER. Backend returns RW buffers in
        // declaration order: gets (RW from arm0), scalar (RW from arm1),
        // result (RW from arm2).
        let read_words = |idx: usize| -> Vec<u32> {
            vyre_primitives::wire::decode_u32_le_bytes_all(&outputs[idx])
        };
        let gets = read_words(0);
        let scalar = read_words(1);
        let result = read_words(2);

        let target_word = (node_id / 32) as usize;
        let bit = 1u32 << (node_id % 32);
        eprintln!(
            "node_id={node_id:>5}  gets[{target_word}]={:#010x}  scalar={}  result[{node_id}]={}",
            gets[target_word], scalar[0], result[node_id as usize]
        );
        // Per-gid hard assertions: with the GridSync fusion + dispatch
        // wrapper, every probe gid must fire  -  including gid 1000 in
        // block 3 which previously failed because the workgroup
        // barrier didn't propagate the gid==0-gated arm 1 write.
        assert_eq!(
            gets[target_word] & bit,
            bit,
            "arm0 atomic_or missed gid {node_id} (gets[{target_word}]={:#010x})",
            gets[target_word]
        );
        assert_eq!(scalar[0], 1, "arm1 bitset_any missed gid {node_id}");
        assert_eq!(
            result[node_id as usize],
            1,
            "arm2 wrapper failed at gid {node_id} block={} \
             (gets[{target_word}]={:#010x}, scalar={}). \
             Cross-grid sync regression: the fusion barrier between \
             arm 1 (gid==0-gated store) and arm 2 (per-gid read) did \
             not propagate the write.",
            node_id / 256,
            gets[target_word],
            scalar[0],
        );
    }
}