use vyre_foundation::ir::{DataType, Program};
use crate::graph::csr_frontier_step::{
csr_queue_step_program, CsrQueueEmit, CsrQueueInputs, CsrQueueLanes, CsrQueueRowPlan,
CsrQueueStepSpec,
};
use crate::graph::csr_queue_strided::CSR_QUEUE_STRIDED_FORWARD_LANES_PER_SOURCE;
pub const CSR_QUEUE_SPLIT_LOW_FORWARD_OP_ID: &str =
"vyre-primitives::graph::csr_queue_split_low_forward_traverse";
pub const CSR_QUEUE_SPLIT_LOW_FORWARD_WORKGROUP_SIZE: [u32; 3] = [256, 1, 1];
pub const CSR_QUEUE_SPLIT_HIGH_DEGREE_THRESHOLD: u32 =
CSR_QUEUE_STRIDED_FORWARD_LANES_PER_SOURCE * CSR_QUEUE_STRIDED_FORWARD_LANES_PER_SOURCE;
#[must_use]
pub const fn csr_queue_split_low_dispatch_grid(queue_capacity: u32) -> [u32; 3] {
let blocks = queue_capacity.div_ceil(CSR_QUEUE_SPLIT_LOW_FORWARD_WORKGROUP_SIZE[0]);
[if blocks == 0 { 1 } else { blocks }, 1, 1]
}
#[must_use]
pub const fn csr_queue_split_mixed_logical_lanes(
queue_capacity: u32,
high_queue_capacity: u32,
) -> u64 {
(queue_capacity as u64).saturating_add(
(high_queue_capacity as u64)
.saturating_mul(CSR_QUEUE_STRIDED_FORWARD_LANES_PER_SOURCE as u64),
)
}
#[derive(Clone, Copy, Debug)]
pub struct CsrQueueSplitLowForwardParams<'a> {
pub active_queue: &'a str,
pub queue_len: &'a str,
pub edge_offsets: &'a str,
pub edge_targets: &'a str,
pub edge_kind_mask: &'a str,
pub frontier_out: &'a str,
pub high_queue: &'a str,
pub high_len: &'a str,
pub node_count: u32,
pub edge_count: u32,
pub queue_capacity: u32,
pub high_queue_capacity: u32,
pub high_degree_threshold: u32,
pub allow_mask: u32,
}
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn csr_queue_split_low_forward_traverse(
active_queue: &str,
queue_len: &str,
edge_offsets: &str,
edge_targets: &str,
edge_kind_mask: &str,
frontier_out: &str,
high_queue: &str,
high_len: &str,
node_count: u32,
edge_count: u32,
queue_capacity: u32,
high_queue_capacity: u32,
high_degree_threshold: u32,
allow_mask: u32,
) -> Program {
csr_queue_split_low_forward_traverse_with(CsrQueueSplitLowForwardParams {
active_queue,
queue_len,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_out,
high_queue,
high_len,
node_count,
edge_count,
queue_capacity,
high_queue_capacity,
high_degree_threshold,
allow_mask,
})
}
#[must_use]
pub fn csr_queue_split_low_forward_traverse_with(
params: CsrQueueSplitLowForwardParams<'_>,
) -> Program {
let CsrQueueSplitLowForwardParams {
active_queue,
queue_len,
edge_offsets,
edge_targets,
edge_kind_mask,
frontier_out,
high_queue,
high_len,
node_count,
edge_count,
queue_capacity,
high_queue_capacity,
high_degree_threshold,
allow_mask,
} = params;
if node_count == 0
|| queue_capacity == 0
|| high_queue_capacity == 0
|| high_degree_threshold == 0
{
return crate::invalid_output_program(CSR_QUEUE_SPLIT_LOW_FORWARD_OP_ID,
frontier_out,
DataType::U32,
format!(
"Fix: csr_queue_split_low_forward_traverse requires node_count > 0, non-zero queue capacities, and high_degree_threshold > 0; got node_count={node_count} queue_capacity={queue_capacity} high_queue_capacity={high_queue_capacity} high_degree_threshold={high_degree_threshold}."
),);
}
csr_queue_step_program(&CsrQueueStepSpec {
op_id: CSR_QUEUE_SPLIT_LOW_FORWARD_OP_ID,
builder_name: "csr_queue_split_low_forward_traverse",
prefix: "qsl",
workgroup_size: CSR_QUEUE_SPLIT_LOW_FORWARD_WORKGROUP_SIZE,
inputs: CsrQueueInputs {
active_queue,
queue_len,
edge_offsets,
edge_targets,
edge_kind_mask,
},
lanes: CsrQueueLanes::Scalar,
row_plan: CsrQueueRowPlan::CompactHighDegree {
high_queue,
high_len,
high_queue_capacity,
high_degree_threshold,
},
emit: CsrQueueEmit::Frontier { frontier_out },
node_count,
edge_count,
queue_capacity,
allow_mask,
})
}
#[cfg(any(test, feature = "cpu-parity"))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CsrQueueSplitLowForwardCpuResult {
pub frontier_out: Vec<u32>,
pub high_queue: Vec<u32>,
pub high_len: u32,
}
#[cfg(any(test, feature = "cpu-parity"))]
#[allow(clippy::too_many_arguments)]
pub fn try_csr_queue_split_low_forward_traverse_cpu(
active_queue: &[u32],
queue_len: u32,
edge_offsets: &[u32],
edge_targets: &[u32],
edge_kind_mask: &[u32],
frontier_out_seed: &[u32],
node_count: u32,
high_queue_capacity: usize,
high_degree_threshold: u32,
allow_mask: u32,
) -> Result<CsrQueueSplitLowForwardCpuResult, String> {
let layout = super::csr_frontier_queue::validate_csr_queue_graph(
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
)?;
if frontier_out_seed.len() != layout.words {
return Err(format!(
"Fix: csr_queue_split_low_forward_traverse requires frontier_out_seed.len() == bitset_words(node_count), got len={} but expected {} for node_count={node_count}.",
frontier_out_seed.len(),
layout.words
));
}
let mut high_queue_probe: Vec<u32> = Vec::new();
crate::graph::scratch::reserve_graph_items(
&mut high_queue_probe,
high_queue_capacity,
"CSR queue split CPU oracle",
"high-degree active queue",
)?;
let mut frontier_out = frontier_out_seed.to_vec();
let mut high_queue = Vec::with_capacity(high_queue_capacity);
let mut high_len = 0_u32;
let take = (queue_len as usize).min(active_queue.len());
for &src in &active_queue[..take] {
if src >= node_count {
continue;
}
let start = edge_offsets[src as usize] as usize;
let end = edge_offsets[src as usize + 1] as usize;
if end.saturating_sub(start) as u32 >= high_degree_threshold {
high_len = high_len.saturating_add(1);
if high_queue.len() < high_queue_capacity {
high_queue.push(src);
continue;
}
}
emit_scalar_row_cpu(
start,
end,
edge_targets,
edge_kind_mask,
node_count,
allow_mask,
&mut frontier_out,
);
}
Ok(CsrQueueSplitLowForwardCpuResult {
frontier_out,
high_queue,
high_len,
})
}
#[cfg(any(test, feature = "cpu-parity"))]
fn emit_scalar_row_cpu(
start: usize,
end: usize,
edge_targets: &[u32],
edge_kind_mask: &[u32],
node_count: u32,
allow_mask: u32,
frontier_out: &mut [u32],
) {
for edge in start..end {
if edge_kind_mask[edge] & allow_mask == 0 {
continue;
}
let dst = edge_targets[edge];
if dst >= node_count {
continue;
}
frontier_out[dst as usize / 32] |= 1_u32 << (dst % 32);
}
}
#[cfg(test)]
mod tests;