Skip to main content

vyre_primitives/predicate/
mod.rs

1//! Frozen predicate primitives  -  the compact engine primitives used by
2//! source-query dialect standard libraries. Each is a thin wrapper that emits a vyre
3//! Program composing [`crate::graph`] + [`crate::bitset`] +
4//! [`crate::label`] primitives with a specific edge-kind mask, tag
5//! mask, or node-kind constant.
6//!
7//! The ten primitives:
8//! - `call_to`  -  edge kind `CallArg` from frontier to callee.
9//! - `return_value_of`  -  edge kind `Return` from call to binding.
10//! - `arg_of`  -  edge kind `CallArg` reverse (arg → call).
11//! - `size_argument_of`  -  arg_of restricted to integer literal args.
12//! - `edge`  -  raw edge matcher (forward, any mask).
13//! - `in_function`  -  node_tags ∩ `TAG_FAMILY_FUNCTION`.
14//! - `in_file`  -  node_tags ∩ `TAG_FAMILY_FILE`.
15//! - `in_package`  -  node_tags ∩ `TAG_FAMILY_PACKAGE`.
16//! - `literal_of`  -  `nodes[v] == NODE_KIND_LITERAL` AND value matches.
17//! - `node_kind`  -  `nodes[v] == kind`.
18
19/// Canonical edge-kind bitmasks matching the shared source-query
20/// `ProgramGraph::EdgeKind`. One bit per kind; multiple bits can
21/// coexist in the same `edge_kind_mask[e]` word.
22pub mod edge_kind {
23    /// Dataflow assignment edge.
24    pub const ASSIGNMENT: u32 = 1 << 0;
25    /// Function-call argument edge.
26    pub const CALL_ARG: u32 = 1 << 1;
27    /// Function return-value edge.
28    pub const RETURN: u32 = 1 << 2;
29    /// SSA Phi edge.
30    pub const PHI: u32 = 1 << 3;
31    /// Dominance edge.
32    pub const DOMINANCE: u32 = 1 << 4;
33    /// Alias edge.
34    pub const ALIAS: u32 = 1 << 5;
35    /// Memory store edge.
36    pub const MEM_STORE: u32 = 1 << 6;
37    /// Memory load edge.
38    pub const MEM_LOAD: u32 = 1 << 7;
39    /// Mutable reference edge.
40    pub const MUT_REF: u32 = 1 << 8;
41    /// Control-flow edge.
42    pub const CONTROL: u32 = 1 << 9;
43
44    // Slot-accessor edges  -  bits 10..14  -  emitted by source frontends'
45    // walker on AST nodes whose semantic operands need direct access
46    // by name (`base_of`, `index_of`, `upper_bound_of`,
47    // `induction_variable_of`, `format_string_argument_of`). The
48    // edge points FROM the parent AST node TO the operand SSA value
49    // so a backward CSR traversal masked on the kind bit picks up
50    // exactly the operand for any node in the input frontier.
51
52    /// Edge from `arr[idx]` → `idx` operand. Emitted on
53    /// subscript_expression / array_access / array_subscript nodes.
54    pub const INDEX: u32 = 1 << 10;
55    /// Edge from `arr[idx]` → `arr` operand.
56    pub const BASE: u32 = 1 << 11;
57    /// Edge from a for/while loop → its induction-variable
58    /// declaration (the `i` in `for (int i = 0; ...; ...)`).
59    pub const INDUCTION_VARIABLE: u32 = 1 << 12;
60    /// Edge from a for/while/do-while loop → its upper-bound
61    /// expression (the right-hand side of the loop test).
62    pub const UPPER_BOUND: u32 = 1 << 13;
63    /// Edge from a printf-family call → its format-string argument.
64    /// The walker consults `printf_family.toml`'s [c.format_slot]
65    /// table to determine which argument slot carries the format
66    /// string (slot 0 for printf, 1 for fprintf/sprintf/snprintf,
67    /// 2 for swprintf, etc.).
68    pub const FORMAT_STRING_ARG: u32 = 1 << 14;
69
70    // Per-slot CALL_ARG subkinds  -  bits 16..23. Pre-fix `arg_of(call,
71    // N)` returned ALL CALL_ARG predecessors regardless of N because
72    // the underlying csr_backward_traverse only filtered by the
73    // generic CALL_ARG bit. With these subkind bits the walker emits
74    // BOTH the generic CALL_ARG bit AND the per-slot bit on each
75    // call-arg edge, and `arg_of(call, N)` masks on
76    // `CALL_ARG_SLOT_BASE << N`. 8 slots cover every realistic
77    // launch-shape arity (every shape uses index ≤ 2). A 9th slot
78    // demand requires widening edge_kind_mask to u64  -  a substrate
79    // change tracked in the open backlog.
80    /// First per-slot call-argument bit. Slot `N` uses
81    /// `CALL_ARG_SLOT_BASE << N` while the generic [`CALL_ARG`] bit remains
82    /// set for recall-safe scans.
83    pub const CALL_ARG_SLOT_BASE: u32 = 1 << 16;
84    /// Edge from a call expression to argument slot 0.
85    pub const CALL_ARG_0: u32 = CALL_ARG_SLOT_BASE;
86    /// Edge from a call expression to argument slot 1.
87    pub const CALL_ARG_1: u32 = CALL_ARG_SLOT_BASE << 1;
88    /// Edge from a call expression to argument slot 2.
89    pub const CALL_ARG_2: u32 = CALL_ARG_SLOT_BASE << 2;
90    /// Edge from a call expression to argument slot 3.
91    pub const CALL_ARG_3: u32 = CALL_ARG_SLOT_BASE << 3;
92    /// Edge from a call expression to argument slot 4.
93    pub const CALL_ARG_4: u32 = CALL_ARG_SLOT_BASE << 4;
94    /// Edge from a call expression to argument slot 5.
95    pub const CALL_ARG_5: u32 = CALL_ARG_SLOT_BASE << 5;
96    /// Edge from a call expression to argument slot 6.
97    pub const CALL_ARG_6: u32 = CALL_ARG_SLOT_BASE << 6;
98    /// Edge from a call expression to argument slot 7.
99    pub const CALL_ARG_7: u32 = CALL_ARG_SLOT_BASE << 7;
100
101    /// Maximum directly-addressable CALL_ARG slot.
102    pub const CALL_ARG_MAX_SLOT: u32 = 7;
103
104    /// Slot-precise edge from a sized-input-read / sized-memory-copy /
105    /// reallocator call to the argument carrying the byte-count
106    /// (recv arg-2, memcpy arg-2, copy_from_user arg-2, realloc arg-1,
107    /// fread arg-1, etc.). Walker emits this edge when the callee has
108    /// an entry in `[<lang>.size_argument_slot]`.
109    /// `size_argument_of($call)` walks back along this single edge
110    /// instead of every CALL_ARG, restoring slot-precise FP elimination
111    /// (every-arg-is-size over-match was the substrate-level FP
112    /// source on every recv / memcpy / copy_from_user shape).
113    pub const SIZE_ARG: u32 = 1 << 24;
114
115    /// Block-membership edge: a CFG basic-block's entry node and every
116    /// AST node contained in that block are joined by a BIDIRECTIONAL
117    /// `BLOCK_MEMBER` pair (`block_entry -> node` and `node ->
118    /// block_entry`). DOMINANCE idom edges only connect block-entry
119    /// nodes to each other, so `dominates($a, $b)` on call-expression
120    /// operands (which hang off blocks via PARENT, not the idom chain)
121    /// would otherwise see an empty dominance graph. The
122    /// `dominator_tree` shim traverses `DOMINANCE | BLOCK_MEMBER`, so a
123    /// backward step from a call node reaches its block entry, walks the
124    /// idom chain, then descends into each dominating block's contained
125    /// nodes, yielding correct block-level dominance for arbitrary
126    /// operands. Deliberately NOT in the CONTROL|DOMINANCE mask the CPU
127    /// dominator bitmap (`sanitized_by`) uses, so that subsystem is
128    /// unaffected.
129    pub const BLOCK_MEMBER: u32 = 1 << 25;
130
131    /// Build the per-slot mask. Slot N maps to
132    /// `CALL_ARG_SLOT_BASE << N` for N in 0..=7. Beyond that the
133    /// caller must fall back to the generic CALL_ARG bit (recall-safe
134    /// but precision-loose) until the substrate widens to u64.
135    #[must_use]
136    pub const fn call_arg_slot(n: u32) -> u32 {
137        if n > CALL_ARG_MAX_SLOT {
138            CALL_ARG
139        } else {
140            CALL_ARG_SLOT_BASE << n
141        }
142    }
143}
144
145/// Canonical tag-family bitmasks matching the shared source-query `TagFamily`.
146pub mod tag_family {
147    /// `in_function` mask.
148    pub const FUNCTION: u32 = 1 << 0;
149    /// `in_file` mask.
150    pub const FILE: u32 = 1 << 1;
151    /// `in_package` mask.
152    pub const PACKAGE: u32 = 1 << 2;
153}
154
155/// Canonical `NodeKind` constants mirroring the shared source-query enum.
156pub mod node_kind {
157    /// `Variable`.
158    pub const VARIABLE: u32 = 1;
159    /// `Call`.
160    pub const CALL: u32 = 2;
161    /// `Import`.
162    pub const IMPORT: u32 = 3;
163    /// `Literal`.
164    pub const LITERAL: u32 = 4;
165    /// `SSA`.
166    pub const SSA: u32 = 5;
167    /// `BasicBlock`.
168    pub const BASIC_BLOCK: u32 = 6;
169    /// `Binary`.
170    pub const BINARY: u32 = 7;
171    /// `FunctionDecl`.
172    pub const FUNCTION_DECL: u32 = 8;
173}
174
175macro_rules! define_tag_family_predicate {
176    (
177        $module:ident,
178        $function:ident,
179        $op_id:literal,
180        $family:expr,
181        $fixture_tags:expr,
182        $expected_nodeset:expr,
183        $doc:literal
184    ) => {
185        #[doc = $doc]
186        pub mod $module {
187            use vyre_foundation::ir::Program;
188
189            use crate::label::resolve_family::resolve_family;
190
191            /// Canonical op id.
192            pub const OP_ID: &str = $op_id;
193
194            /// Build the canonical tag-family predicate program.
195            #[must_use]
196            pub fn $function(node_tags: &str, nodeset_out: &str, node_count: u32) -> Program {
197                vyre_foundation::composition::tag_program(
198                    OP_ID,
199                    resolve_family(node_tags, nodeset_out, node_count, $family),
200                )
201            }
202
203            /// CPU reference.
204            #[must_use]
205            #[cfg(any(test, feature = "cpu-parity"))]
206            pub fn cpu_ref(node_tags: &[u32]) -> Vec<u32> {
207                crate::label::resolve_family::cpu_ref(node_tags, $family)
208            }
209
210            #[cfg(feature = "inventory-registry")]
211            inventory::submit! {
212                vyre_foundation::operation::OperationRegistration::primitive(
213                    OP_ID,
214                    || $function("tags", "nodeset", 4),
215                    Some(|| {
216                        let to_bytes = crate::predicate::inventory_u32_le_bytes;
217                        vec![vec![
218                            to_bytes($fixture_tags),
219                            to_bytes(&[0]),
220                        ]]
221                    }),
222                    Some(|| {
223                        let to_bytes = crate::predicate::inventory_u32_le_bytes;
224                        vec![vec![to_bytes($expected_nodeset)]]
225                    }),
226                )
227            }
228
229            #[cfg(test)]
230            mod tests {
231                use super::*;
232
233                #[test]
234                fn cpu_ref_matches_inventory_fixture() {
235                    assert_eq!(cpu_ref($fixture_tags), $expected_nodeset.to_vec());
236                }
237            }
238        }
239    };
240}
241
242macro_rules! define_fixed_forward_edge_predicate {
243    (
244        $module:ident,
245        $function:ident,
246        $op_id:literal,
247        $edge_mask:expr,
248        $edge_count:expr,
249        $fixture_edge_offsets:expr,
250        $fixture_edge_targets:expr,
251        $fixture_edge_masks:expr,
252        $expected_nodeset:expr,
253        $module_doc:literal,
254        $function_doc:literal,
255        $region_label:literal
256    ) => {
257        #[doc = $module_doc]
258        pub mod $module {
259            use vyre_foundation::ir::Program;
260
261            use crate::graph::program_graph::ProgramGraphShape;
262            use crate::predicate::traversal::forward_edge_program;
263            #[cfg(any(test, feature = "cpu-parity"))]
264            use crate::predicate::traversal::{cpu_ref_forward, cpu_ref_forward_into};
265
266            /// Canonical op id.
267            pub const OP_ID: &str = $op_id;
268
269            #[doc = $function_doc]
270            #[must_use]
271            pub fn $function(
272                shape: ProgramGraphShape,
273                frontier_in: &str,
274                frontier_out: &str,
275            ) -> Program {
276                forward_edge_program(OP_ID, shape, frontier_in, frontier_out, $edge_mask)
277            }
278
279            /// CPU reference.
280            #[must_use]
281            #[cfg(any(test, feature = "cpu-parity"))]
282            pub fn cpu_ref(
283                node_count: u32,
284                edge_offsets: &[u32],
285                edge_targets: &[u32],
286                edge_kind_mask: &[u32],
287                frontier_in: &[u32],
288            ) -> Vec<u32> {
289                cpu_ref_forward(
290                    node_count,
291                    edge_offsets,
292                    edge_targets,
293                    edge_kind_mask,
294                    frontier_in,
295                    $edge_mask,
296                )
297            }
298
299            /// CPU reference using caller-owned output storage.
300            #[cfg(any(test, feature = "cpu-parity"))]
301            pub fn cpu_ref_into(
302                node_count: u32,
303                edge_offsets: &[u32],
304                edge_targets: &[u32],
305                edge_kind_mask: &[u32],
306                frontier_in: &[u32],
307                out: &mut Vec<u32>,
308            ) {
309                cpu_ref_forward_into(
310                    node_count,
311                    edge_offsets,
312                    edge_targets,
313                    edge_kind_mask,
314                    frontier_in,
315                    $edge_mask,
316                    out,
317                );
318            }
319
320            #[cfg(feature = "inventory-registry")]
321            inventory::submit! {
322                vyre_foundation::operation::OperationRegistration::primitive(
323                    OP_ID,
324                    || $function(ProgramGraphShape::new(4, $edge_count), "fin", "fout"),
325                    Some(|| {
326                        let b = crate::predicate::inventory_u32_le_bytes;
327                        vec![vec![
328                            b(&[2, 1, 1, 1]),
329                            b($fixture_edge_offsets),
330                            b($fixture_edge_targets),
331                            b($fixture_edge_masks),
332                            b(&[0, 0, 0, 0]),
333                            b(&[0b0001]),
334                            b(&[0]),
335                        ]]
336                    }),
337                    Some(|| {
338                        let b = crate::predicate::inventory_u32_le_bytes;
339                        vec![vec![b($expected_nodeset)]]
340                    }),
341                )
342            }
343
344            #[cfg(test)]
345            mod tests {
346                use super::*;
347                use crate::predicate::traversal::assert_region_op_id;
348
349                #[test]
350                fn preserves_wrapper_op_id() {
351                    let program = $function(ProgramGraphShape::new(4, $edge_count), "fin", "fout");
352                    assert_region_op_id(&program, OP_ID, $region_label);
353                }
354            }
355        }
356    };
357}
358
359pub mod arg_of;
360define_fixed_forward_edge_predicate!(
361    call_to,
362    call_to,
363    "vyre-primitives::predicate::call_to",
364    crate::predicate::edge_kind::CALL_ARG,
365    2,
366    &[0, 1, 2, 2, 2],
367    &[1, 2],
368    &[2, 2],
369    &[0b0010],
370    "`call_to` - forward-traverse along `CALL_ARG` edges.",
371    "Build a Program that emits the callee NodeSet reachable via `CallArg` edges from the input frontier.",
372    "call_to"
373);
374pub mod edge;
375mod traversal;
376define_tag_family_predicate!(
377    in_file,
378    in_file,
379    "vyre-primitives::predicate::in_file",
380    crate::predicate::tag_family::FILE,
381    &[2, 2, 0, 0],
382    &[0b0011],
383    "`in_file` - NodeSet of file-tagged nodes."
384);
385define_tag_family_predicate!(
386    in_function,
387    in_function,
388    "vyre-primitives::predicate::in_function",
389    crate::predicate::tag_family::FUNCTION,
390    &[1, 0, 1, 0],
391    &[0b0101],
392    "`in_function` - NodeSet of function-tagged nodes."
393);
394define_tag_family_predicate!(
395    in_package,
396    in_package,
397    "vyre-primitives::predicate::in_package",
398    crate::predicate::tag_family::PACKAGE,
399    &[4, 0, 4, 0],
400    &[0b0101],
401    "`in_package` - NodeSet of package-tagged nodes."
402);
403pub mod literal_of;
404pub mod node_kind_eq;
405define_fixed_forward_edge_predicate!(
406    return_value_of,
407    return_value_of,
408    "vyre-primitives::predicate::return_value_of",
409    crate::predicate::edge_kind::RETURN,
410    1,
411    &[0, 1, 1, 1, 1],
412    &[1],
413    &[4],
414    &[0b0010],
415    "`return_value_of` - forward-traverse along `RETURN` edges.",
416    "Build a Program that emits the NodeSet of return-value bindings reached from the caller frontier via `Return` edges.",
417    "return_value_of"
418);
419pub mod size_argument_of;
420
421/// Little-endian `u32` word packing for [`inventory::submit!`] GPU fixtures.
422///
423/// Centralizes the repeated `to_le_bytes` flatten used by every graph
424/// predicate's registry block (`audits/VYRE_PRIMITIVES_GAPS.md` dedup).
425#[cfg(feature = "inventory-registry")]
426pub(crate) fn inventory_u32_le_bytes(words: &[u32]) -> Vec<u8> {
427    crate::wire::pack_u32_slice(words)
428}