use super::U32_BITS_PER_WORD;
#[inline]
pub(crate) fn require_bitset_words(
stage: &str,
values: &[u32],
words: usize,
) -> Result<(), String> {
if values.len() != words {
return Err(format!(
"{stage} bitset has {} u32 words, expected exactly {words}. Fix: pass the declared semantic bitset width; Weir GPU wrappers never silently pad or truncate facts.",
values.len()
));
}
Ok(())
}
#[inline]
#[must_use]
pub(crate) const fn bitset_words(bits: u32) -> u32 {
let full_words = bits / U32_BITS_PER_WORD;
if bits % U32_BITS_PER_WORD == 0 {
full_words
} else {
full_words + 1
}
}
#[inline]
pub(crate) fn bitset_word_capacity(stage: &str, bits: u32) -> Result<usize, String> {
usize::try_from(bitset_words(bits)).map_err(|source| {
format!(
"{stage} bitset word count cannot fit usize: {source}. Fix: shard the dataflow domain before host scratch allocation."
)
})
}
#[inline]
pub(crate) fn u32_to_usize(value: u32, label: &str) -> Result<usize, String> {
usize::try_from(value).map_err(|source| {
format!(
"{label} cannot fit host usize: {source}. Fix: shard the dataflow problem before GPU dispatch."
)
})
}
#[inline]
pub(crate) fn require_bitset_tail_clear(
stage: &str,
values: &[u32],
domain_bits: u32,
) -> Result<(), String> {
if domain_bits == 0 {
if values.iter().any(|word| *word != 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 % U32_BITS_PER_WORD;
if tail == 0 || values.is_empty() {
return Ok(());
}
let allowed = (1u32 << tail) - 1;
let actual = *values.last().unwrap_or(&0);
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(())
}
#[inline]
pub(crate) fn require_positive_iterations(stage: &str, max_iterations: u32) -> Result<(), String> {
if max_iterations == 0 {
return Err(format!(
"{stage} received max_iterations=0. Fix: pass a positive fixpoint iteration budget; Weir GPU wrappers reject impossible convergence budgets before dispatch setup."
));
}
Ok(())
}
#[inline]
pub(crate) fn require_csr_shape(
stage: &str,
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
) -> Result<(), String> {
require_csr_offsets_targets(stage, node_count, edge_offsets, edge_targets)?;
let nodes = usize::try_from(node_count).map_err(|_| {
format!("{stage} node_count={node_count} cannot fit usize. Fix: shard the graph before dispatch.")
})?;
let edge_count = u32_to_usize(edge_offsets[nodes], "final CSR offset")?;
if edge_kind_mask.len() != edge_count {
return Err(format!(
"{stage} declares {edge_count} edges but edge_kind_mask has {}. Fix: pass edge-kind arrays matching the final offset exactly.",
edge_kind_mask.len()
));
}
Ok(())
}
#[inline]
pub(crate) fn require_csr_offsets_targets(
stage: &str,
node_count: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
) -> Result<(), String> {
let nodes = usize::try_from(node_count).map_err(|_| {
format!("{stage} node_count={node_count} cannot fit usize. Fix: shard the graph before dispatch.")
})?;
let expected_offsets = nodes.checked_add(1).ok_or_else(|| {
format!("{stage} node_count={node_count} overflows when adding CSR sentinel offset.")
})?;
if edge_offsets.len() != expected_offsets {
return Err(format!(
"{stage} edge_offsets has {} entries, expected exactly node_count + 1 = {expected_offsets}. Fix: pass a complete CSR offset table.",
edge_offsets.len()
));
}
let edge_count = u32_to_usize(edge_offsets[nodes], "final CSR offset")?;
if edge_targets.len() != edge_count {
return Err(format!(
"{stage} declares {edge_count} edges but edge_targets has {}. Fix: pass edge arrays matching the final offset exactly.",
edge_targets.len()
));
}
if edge_offsets[0] != 0 {
return Err(format!(
"{stage} edge_offsets[0] is {}, expected 0. Fix: rebuild CSR offsets from zero; Weir GPU wrappers reject orphan prefix edges.",
edge_offsets[0]
));
}
for node in 0..nodes {
let start = u32_to_usize(edge_offsets[node], "CSR row start offset")?;
let end = u32_to_usize(edge_offsets[node + 1], "CSR row end offset")?;
if start > end {
return Err(format!(
"{stage} edge_offsets is not monotonic at node {node}: start={start}, end={end}. Fix: rebuild CSR offsets."
));
}
if end > edge_count {
return Err(format!(
"{stage} edge_offsets[{}]={end} exceeds declared edge count {edge_count}. Fix: rebuild CSR offsets.",
node + 1
));
}
for (edge, &target) in edge_targets.iter().enumerate().take(end).skip(start) {
if target >= node_count {
return Err(format!(
"{stage} edge {edge} targets node {target}, but node_count is {node_count}. Fix: reject or remap malformed graph edges before dispatch."
));
}
}
}
Ok(())
}