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}