Skip to main content

vyre_primitives/graph/exploded/
dispatch_plan.rs

1use super::abi::{ifds_csr_dispatch_grid, IFDS_CSR_EMPTY_DISPATCH_GRID};
2use super::layout::{
3    IfdsCsrLayout, IfdsCsrProgramCacheKey, IfdsCsrRuleInputFingerprint, IfdsCsrStaticInputKey,
4};
5use super::program_ir::build_ifds_csr_program;
6use super::validation::validate_ifds_csr_inputs;
7use vyre_foundation::ir::Program;
8
9/// Primitive-owned dispatch plan for exploded IFDS CSR construction.
10///
11/// Consumers own only rule marshalling and backend invocation. Buffer labels,
12/// padded storage widths, readback widths, and grid shape live here so
13/// self-substrate and future consumers cannot fork the dispatch contract.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct IfdsCsrDispatchPlan {
16    /// Validated CSR layout and rule counts.
17    pub layout: IfdsCsrLayout,
18    /// Primitive-owned generated-Program cache identity.
19    pub program_key: IfdsCsrProgramCacheKey,
20    /// Dispatch grid override.
21    pub grid: [u32; 3],
22    /// Padded words for each intra edge field.
23    pub intra_field_words: usize,
24    /// Padded words for each inter edge field.
25    pub inter_field_words: usize,
26    /// Padded words for each GEN field.
27    pub gen_field_words: usize,
28    /// Padded words for each KILL field.
29    pub kill_field_words: usize,
30    /// Words in the CSR row-pointer output.
31    pub row_ptr_words: usize,
32    /// Words in the row-cursor scratch output.
33    pub row_cursor_words: usize,
34    /// Words in the dense kill-bitmap scratch buffer.
35    pub killed_words: usize,
36    /// Words in the CSR column-index output.
37    pub col_idx_words: usize,
38    /// Words in the emitted-column-length output.
39    pub col_len_words: usize,
40    /// Maximum legal emitted column count.
41    pub max_col_count: u32,
42}
43
44impl IfdsCsrDispatchPlan {
45    /// Build the GPU program for this validated dispatch plan.
46    #[must_use]
47    pub fn program(&self) -> Program {
48        build_ifds_csr_program(
49            self.layout.num_procs,
50            self.layout.blocks_per_proc,
51            self.layout.facts_per_proc,
52            self.layout.intra_count,
53            self.layout.inter_count,
54            self.layout.gen_count,
55            self.layout.kill_count,
56            self.layout.max_col_count,
57        )
58    }
59
60    /// Stable generated-Program cache identity for this dispatch shape.
61    #[must_use]
62    pub const fn program_cache_key(&self) -> IfdsCsrProgramCacheKey {
63        self.program_key
64    }
65
66    /// Stable identity for static rule uploads under this dispatch plan.
67    #[must_use]
68    pub const fn static_input_key(
69        &self,
70        rule_fingerprint: IfdsCsrRuleInputFingerprint,
71    ) -> IfdsCsrStaticInputKey {
72        IfdsCsrStaticInputKey {
73            program_key: self.program_key,
74            rule_fingerprint,
75        }
76    }
77}
78/// Validate caller-owned IFDS rules and return the complete primitive dispatch plan.
79pub fn plan_ifds_csr_dispatch(
80    num_procs: u32,
81    blocks_per_proc: u32,
82    facts_per_proc: u32,
83    intra_edges: &[(u32, u32, u32)],
84    inter_edges: &[(u32, u32, u32, u32)],
85    flow_gen: &[(u32, u32, u32)],
86    flow_kill: &[(u32, u32, u32)],
87) -> Result<IfdsCsrDispatchPlan, String> {
88    let layout = validate_ifds_csr_inputs(
89        num_procs,
90        blocks_per_proc,
91        facts_per_proc,
92        intra_edges,
93        inter_edges,
94        flow_gen,
95        flow_kill,
96    )?;
97    Ok(IfdsCsrDispatchPlan {
98        intra_field_words: layout.intra_storage_words,
99        inter_field_words: layout.inter_storage_words,
100        gen_field_words: layout.gen_storage_words,
101        kill_field_words: layout.kill_storage_words,
102        row_ptr_words: layout.row_words,
103        row_cursor_words: layout.row_cursor_words,
104        killed_words: layout.killed_words,
105        col_idx_words: layout.col_buffer_words,
106        col_len_words: 1,
107        max_col_count: layout.max_col_count,
108        program_key: IfdsCsrProgramCacheKey::from_layout(&layout),
109        layout,
110        grid: if layout.empty {
111            IFDS_CSR_EMPTY_DISPATCH_GRID
112        } else {
113            ifds_csr_dispatch_grid(layout.intra_count, layout.total_nodes)
114        },
115    })
116}
117
118/// Caller-owned structure-of-arrays rule columns for IFDS CSR dispatch.
119///
120/// This lives with the primitive dispatch plan because field order, padding,
121/// and rule-domain grouping are part of the primitive ABI. Dispatch consumers
122/// reuse one value across calls and upload these columns without re-forking
123/// IFDS tuple marshalling.
124#[derive(Debug, Default, Clone, PartialEq, Eq)]
125pub struct IfdsCsrRuleColumns {
126    /// Intra-procedural procedure ids.
127    pub intra_proc: Vec<u32>,
128    /// Intra-procedural source blocks.
129    pub intra_src_block: Vec<u32>,
130    /// Intra-procedural destination blocks.
131    pub intra_dst_block: Vec<u32>,
132    /// Inter-procedural source procedures.
133    pub inter_src_proc: Vec<u32>,
134    /// Inter-procedural source blocks.
135    pub inter_src_block: Vec<u32>,
136    /// Inter-procedural destination procedures.
137    pub inter_dst_proc: Vec<u32>,
138    /// Inter-procedural destination blocks.
139    pub inter_dst_block: Vec<u32>,
140    /// GEN rule procedures.
141    pub gen_proc: Vec<u32>,
142    /// GEN rule blocks.
143    pub gen_block: Vec<u32>,
144    /// GEN rule facts.
145    pub gen_fact: Vec<u32>,
146    /// KILL rule procedures.
147    pub kill_proc: Vec<u32>,
148    /// KILL rule blocks.
149    pub kill_block: Vec<u32>,
150    /// KILL rule facts.
151    pub kill_fact: Vec<u32>,
152}
153
154impl IfdsCsrRuleColumns {
155    /// Split all IFDS tuple rules into primitive-owned structure-of-arrays
156    /// columns, reusing existing allocations.
157    ///
158    /// # Errors
159    ///
160    /// Returns an allocation diagnostic if any output column cannot reserve
161    /// enough space for its incoming rule slice.
162    pub fn prepare(
163        &mut self,
164        intra_edges: &[(u32, u32, u32)],
165        inter_edges: &[(u32, u32, u32, u32)],
166        flow_gen: &[(u32, u32, u32)],
167        flow_kill: &[(u32, u32, u32)],
168    ) -> Result<(), String> {
169        split_ifds_rule_triples_into(
170            intra_edges,
171            &mut self.intra_proc,
172            &mut self.intra_src_block,
173            &mut self.intra_dst_block,
174            "IFDS intra edge columns",
175        )?;
176        split_ifds_rule_quads_into(
177            inter_edges,
178            &mut self.inter_src_proc,
179            &mut self.inter_src_block,
180            &mut self.inter_dst_proc,
181            &mut self.inter_dst_block,
182            "IFDS inter edge columns",
183        )?;
184        split_ifds_rule_triples_into(
185            flow_gen,
186            &mut self.gen_proc,
187            &mut self.gen_block,
188            &mut self.gen_fact,
189            "IFDS GEN columns",
190        )?;
191        split_ifds_rule_triples_into(
192            flow_kill,
193            &mut self.kill_proc,
194            &mut self.kill_block,
195            &mut self.kill_fact,
196            "IFDS KILL columns",
197        )
198    }
199}
200
201fn prepare_ifds_rule_columns(
202    columns: &mut [&mut Vec<u32>],
203    rule_count: usize,
204    context: &str,
205) -> Result<(), String> {
206    for column in columns {
207        column.clear();
208        crate::graph::scratch::reserve_graph_items(
209            column,
210            rule_count,
211            "exploded IFDS primitive",
212            context,
213        )?;
214    }
215    Ok(())
216}
217fn split_ifds_rules_into<const WIDTH: usize, Rule: Copy>(
218    rules: &[Rule],
219    mut columns: [&mut Vec<u32>; WIDTH],
220    fields: impl Fn(Rule) -> [u32; WIDTH],
221    context: &str,
222) -> Result<(), String> {
223    prepare_ifds_rule_columns(&mut columns, rules.len(), context)?;
224    for &rule in rules {
225        for (column, value) in columns.iter_mut().zip(fields(rule)) {
226            column.push(value);
227        }
228    }
229    Ok(())
230}
231
232/// Split IFDS triple rules into primitive-owned structure-of-arrays columns.
233///
234/// This keeps the rule-column layout beside the dispatch plan so wrappers do
235/// not reimplement tuple marshalling before uploading GPU buffers.
236///
237/// # Errors
238///
239/// Returns an allocation diagnostic if any output column cannot reserve enough
240/// space for the incoming rules.
241pub fn split_ifds_rule_triples_into(
242    triples: &[(u32, u32, u32)],
243    first: &mut Vec<u32>,
244    second: &mut Vec<u32>,
245    third: &mut Vec<u32>,
246    context: &str,
247) -> Result<(), String> {
248    split_ifds_rules_into(
249        triples,
250        [first, second, third],
251        |(a, b, c)| [a, b, c],
252        context,
253    )
254}
255
256/// Split IFDS quadruple rules into primitive-owned structure-of-arrays columns.
257///
258/// # Errors
259///
260/// Returns an allocation diagnostic if any output column cannot reserve enough
261/// space for the incoming rules.
262pub fn split_ifds_rule_quads_into(
263    quads: &[(u32, u32, u32, u32)],
264    first: &mut Vec<u32>,
265    second: &mut Vec<u32>,
266    third: &mut Vec<u32>,
267    fourth: &mut Vec<u32>,
268    context: &str,
269) -> Result<(), String> {
270    split_ifds_rules_into(
271        quads,
272        [first, second, third, fourth],
273        |(a, b, c, d)| [a, b, c, d],
274        context,
275    )
276}