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}"
);
}
}