weirflow 0.1.0

GPU-first dataflow analysis primitives for Vyre and Santh compiler pipelines.
Documentation
//! IFDS frontier bitset sizing, validation, and encoded-node decoding.

use crate::ifds_gpu::IfdsShape;
use vyre_primitives::graph::exploded::dense_to_encoded;
use vyre_primitives::wire::read_u32_le_word;

pub(crate) fn try_ifds_words(node_count: u32) -> Result<usize, String> {
    crate::dispatch_decode::bitset_word_capacity("IFDS frontier", node_count)
}

#[cfg(test)]
pub(crate) fn ifds_words(node_count: u32) -> usize {
    try_ifds_words(node_count).unwrap_or_else(|error| panic!("{error}"))
}

pub(crate) fn ifds_encoded_frontier_nodes_into(
    bits: &[u32],
    node_count: u32,
    shape: IfdsShape,
    out: &mut Vec<u32>,
) -> Result<(), String> {
    out.clear();
    let node_count = crate::dispatch_decode::u32_to_usize(node_count, "IFDS frontier node count")?;
    for (word_index, &word) in bits.iter().enumerate() {
        if !push_encoded_frontier_word(word_index, word, node_count, shape, out)? {
            break;
        }
    }
    out.sort_unstable();
    Ok(())
}

pub(crate) fn ifds_encoded_frontier_nodes_from_le_bytes_into(
    bytes: &[u8],
    word_count: usize,
    node_count: u32,
    shape: IfdsShape,
    out: &mut Vec<u32>,
) -> Result<(), String> {
    let expected_len = word_count.checked_mul(std::mem::size_of::<u32>()).ok_or_else(|| {
        "weir IFDS frontier byte length overflows usize. Fix: shard the IFDS graph before solving."
            .to_string()
    })?;
    if bytes.len() != expected_len {
        return Err(format!(
            "ifds resident parallel batch final frontier slice has {} bytes, expected {expected_len}. Fix: backend returned a malformed frontier matrix.",
            bytes.len()
        ));
    }
    require_bitset_tail_clear_le_bytes(
        "ifds resident parallel batch output",
        bytes,
        word_count,
        node_count,
    )?;
    out.clear();
    let node_count =
        crate::dispatch_decode::u32_to_usize(node_count, "IFDS resident frontier node count")?;
    for word_index in 0..word_count {
        let word = read_u32_le_word(bytes, word_index, "IFDS resident frontier word")?;
        if !push_encoded_frontier_word(word_index, word, node_count, shape, out)? {
            break;
        }
    }
    out.sort_unstable();
    Ok(())
}

fn push_encoded_frontier_word(
    word_index: usize,
    word: u32,
    node_count: usize,
    shape: IfdsShape,
    out: &mut Vec<u32>,
) -> Result<bool, String> {
    let dense_base = word_index.checked_mul(32).ok_or_else(|| {
        "IFDS frontier dense base overflowed host indexing. Fix: shard the frontier before decoding."
            .to_string()
    })?;
    if dense_base >= node_count {
        return Ok(false);
    }
    let mut remaining = word;
    if remaining != 0 {
        let additional = remaining.count_ones() as usize;
        let reserved_len = out.len().checked_add(additional).ok_or_else(|| {
            "IFDS frontier decoded node count overflows host indexing. Fix: shard the frontier before decoding."
                .to_string()
        })?;
        crate::staging_reserve::reserve_vec(out, reserved_len, "IFDS frontier decoded node")?;
    }
    while remaining != 0 {
        let bit = remaining.trailing_zeros() as usize;
        let dense = dense_base.checked_add(bit).ok_or_else(|| {
            "IFDS frontier dense node id overflowed host indexing. Fix: shard the frontier before decoding."
                .to_string()
        })?;
        if dense < node_count {
            let dense_u32 = u32::try_from(dense).map_err(|error| {
                format!(
                    "IFDS frontier dense node id {dense} cannot fit u32: {error}. Fix: shard the IFDS graph before decoding."
                )
            })?;
            let encoded = dense_to_encoded(dense_u32, shape.blocks_per_proc, shape.facts_per_proc)
                .ok_or_else(|| {
                    format!(
                        "IFDS frontier dense node {dense_u32} is inside node_count={node_count} but cannot be mapped with blocks_per_proc={} facts_per_proc={}. Fix: rebuild the IFDS shape so dense frontier bits and encoded node layout share one domain.",
                        shape.blocks_per_proc,
                        shape.facts_per_proc
                    )
                })?;
            out.push(encoded);
        }
        remaining &= remaining - 1;
    }
    Ok(true)
}

pub(crate) fn require_bitset_tail_clear_le_bytes(
    stage: &str,
    bytes: &[u8],
    word_count: usize,
    domain_bits: u32,
) -> Result<(), String> {
    let expected_len = word_count
        .checked_mul(std::mem::size_of::<u32>())
        .ok_or_else(|| {
            format!(
                "{stage} byte length overflows usize. Fix: shard the IFDS graph before solving."
            )
        })?;
    if bytes.len() != expected_len {
        return Err(format!(
            "{stage} bitset has {} bytes, expected exactly {expected_len}. Fix: backend returned a malformed frontier slice.",
            bytes.len()
        ));
    }
    if domain_bits == 0 {
        if bytes.iter().any(|byte| *byte != 0) {
            return Err(format!(
                "{stage} bitset sets bits outside the declared empty domain: values must be empty or all-zero. Fix: pass zero semantic words for empty domains before GPU dispatch."
            ));
        }
        return Ok(());
    }
    let tail = domain_bits % 32;
    if tail == 0 || word_count == 0 {
        return Ok(());
    }
    let actual = read_u32_le_word(bytes, word_count - 1, stage)?;
    let allowed = (1u32 << tail) - 1;
    if actual & !allowed != 0 {
        return Err(format!(
            "{stage} seed bitset sets bits outside the declared domain of {domain_bits} bits: last word {actual:#010x}, allowed mask {allowed:#010x}. Fix: clear out-of-domain tail bits before GPU dispatch."
        ));
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn tail_clear_rejects_out_of_domain_bits_after_canonical_word_decode() {
        require_bitset_tail_clear_le_bytes("ifds test", &[0x01, 0, 0, 0], 1, 1)
            .expect("single in-domain bit should pass");

        let err = require_bitset_tail_clear_le_bytes("ifds test", &[0x02, 0, 0, 0], 1, 1)
            .expect_err("tail bit outside one-bit domain must fail");
        assert!(
            err.contains("outside the declared domain"),
            "unexpected diagnostic: {err}"
        );
    }

    #[test]
    fn tail_clear_rejects_truncated_backend_words_before_decode() {
        let err = require_bitset_tail_clear_le_bytes("ifds test", &[0x01, 0, 0], 1, 1)
            .expect_err("truncated backend word must fail before indexed decode");
        assert!(
            err.contains("expected exactly 4"),
            "unexpected diagnostic: {err}"
        );
    }
}