#[allow(clippy::type_complexity)]
pub(super) fn live_reverse_control_csr(
node_count: usize,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
) -> Result<(Vec<u32>, Vec<u32>, Vec<u32>), String> {
let mut scratch = crate::fixed_point_scratch::FixedPointScratch::default();
live_reverse_control_csr_into(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
&mut scratch,
)?;
Ok((
std::mem::take(&mut scratch.reverse_offsets),
std::mem::take(&mut scratch.reverse_targets),
std::mem::take(&mut scratch.reverse_masks),
))
}
pub(super) fn live_reverse_control_csr_into(
node_count: usize,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
scratch: &mut crate::fixed_point_scratch::FixedPointScratch,
) -> Result<(), String> {
let expected_offsets = node_count.checked_add(1).ok_or_else(|| {
"live_closure CSR node_count overflows usize when adding sentinel offset".to_string()
})?;
if edge_offsets.len() != expected_offsets {
return Err(format!(
"live_closure CSR edge_offsets has {} entries, expected exactly node_count + 1 = {expected_offsets}. Fix: pass a complete CSR offset table.",
edge_offsets.len()
));
}
let edge_count = live_u32_to_usize(edge_offsets[node_count], "final CSR offset")?;
if edge_targets.len() != edge_count || edge_kind_mask.len() != edge_count {
return Err(format!(
"live_closure CSR declares {edge_count} edges but edge_targets has {} and edge_kind_mask has {}. Fix: pass edge arrays matching the final offset exactly.",
edge_targets.len(),
edge_kind_mask.len()
));
}
scratch.prepare_reverse_csr_scratch(node_count, edge_count)?;
if edge_offsets[0] != 0 {
return Err(format!(
"live_closure CSR edge_offsets[0] is {}, expected 0. Fix: rebuild CSR offsets from zero; Weir GPU wrappers reject orphan prefix edges.",
edge_offsets[0]
));
}
for src in 0..node_count {
let start = live_u32_to_usize(edge_offsets[src], "row start offset")?;
let end = live_u32_to_usize(edge_offsets[src + 1], "row end offset")?;
if start > end {
return Err(format!(
"live_closure CSR edge_offsets is not monotonic at node {src}: start={start}, end={end}. Fix: sort and rebuild CSR offsets."
));
}
if end > edge_count {
return Err(format!(
"live_closure CSR edge_offsets[{}]={end} exceeds declared edge count {edge_count}. Fix: rebuild CSR offsets from the edge arrays.",
src + 1
));
}
for edge in start..end {
if edge_kind_mask[edge] & vyre_primitives::predicate::edge_kind::CONTROL == 0 {
continue;
}
let dst = edge_targets[edge];
let dst = live_u32_to_usize(dst, "edge target")?;
if dst >= node_count {
return Err(format!(
"live_closure CSR edge {edge} targets node {dst}, but node_count is {node_count}. Fix: reject or remap malformed graph edges before dispatch."
));
}
scratch.reverse_indegree[dst] = scratch.reverse_indegree[dst].checked_add(1).ok_or_else(|| {
format!(
"live_closure reverse indegree overflowed u32 for node {dst}. Fix: shard the graph before live-closure dispatch."
)
})?;
}
}
for node in 0..node_count {
let indegree = live_usize_to_u32(scratch.reverse_indegree[node], "reverse indegree")?;
scratch.reverse_offsets[node + 1] =
scratch.reverse_offsets[node].checked_add(indegree).ok_or_else(|| {
format!(
"live_closure reverse offset overflowed u32 at node {node}. Fix: shard the graph before live-closure dispatch."
)
})?;
}
for (dst, src) in scratch
.reverse_cursor
.iter_mut()
.zip(scratch.reverse_offsets.iter().copied())
{
*dst = live_u32_to_usize(src, "reverse cursor")?;
}
let edge_total = live_u32_to_usize(scratch.reverse_offsets[node_count], "reverse edge count")?;
crate::dispatch_decode::try_write_zero_words(
&mut scratch.reverse_targets,
edge_total,
"live reverse CSR target",
)?;
crate::dispatch_decode::try_write_zero_words(
&mut scratch.reverse_masks,
edge_total,
"live reverse CSR mask",
)?;
for src in 0..node_count {
let start = live_u32_to_usize(edge_offsets[src], "row start offset")?;
let end = live_u32_to_usize(edge_offsets[src + 1], "row end offset")?;
for edge in start..end {
if edge_kind_mask[edge] & vyre_primitives::predicate::edge_kind::CONTROL == 0 {
continue;
}
let dst = edge_targets[edge];
let dst = live_u32_to_usize(dst, "edge target")?;
let out = scratch.reverse_cursor[dst];
if out >= scratch.reverse_targets.len() {
return Err(format!(
"live_closure reverse cursor for node {dst} points to {out}, outside {} reverse target slots. Fix: rebuild reverse CSR offsets before dispatch.",
scratch.reverse_targets.len()
));
}
scratch.reverse_cursor[dst] = scratch.reverse_cursor[dst].checked_add(1).ok_or_else(|| {
format!(
"live_closure reverse cursor overflowed usize for node {dst}. Fix: shard the graph before live-closure dispatch."
)
})?;
scratch.reverse_targets[out] = u32::try_from(src).map_err(|error| {
format!(
"live_closure source node {src} cannot fit u32: {error}. Fix: shard the graph before live-closure dispatch."
)
})?;
scratch.reverse_masks[out] = vyre_primitives::predicate::edge_kind::CONTROL;
}
}
if scratch.reverse_targets.is_empty() {
if let Some(last_offset) = scratch.reverse_offsets.last_mut() {
*last_offset = 1;
}
scratch.reverse_targets.push(0);
scratch.reverse_masks.push(0);
}
Ok(())
}
fn live_u32_to_usize(value: u32, label: &'static str) -> Result<usize, String> {
usize::try_from(value).map_err(|error| {
format!(
"live_closure {label} value {value} cannot fit usize: {error}. Fix: shard the graph before live-closure dispatch."
)
})
}
fn live_usize_to_u32(value: usize, label: &'static str) -> Result<u32, String> {
u32::try_from(value).map_err(|error| {
format!(
"live_closure {label} value {value} cannot fit u32: {error}. Fix: shard the graph before live-closure dispatch."
)
})
}