Skip to main content

xlog_runtime/
resident_graph.rs

1//! Device-resident execution for bounded ordinary Datalog plans.
2//!
3//! The resident route is deliberately fail-closed.  Plan inspection records
4//! every physical route occurrence before setup allocates workspace or
5//! enqueues CUDA work.  Unsupported shapes remain on the existing executor.
6
7use std::collections::{BTreeSet, HashMap};
8use std::fmt::{self, Write};
9use std::sync::Arc;
10
11#[cfg(all(test, feature = "resident-graph-tests"))]
12use std::cell::Cell;
13
14use xlog_core::{RelId, Result, ScalarType, Schema};
15use xlog_ir::{ConstValue, ExecutionPlan, Expr, JoinType, ProjectExpr, RirNode};
16
17const RESIDENT_GRAPH_MAX_INTERMEDIATE_ARITY: usize = 17;
18
19/// Schemas keyed by the relation identities carried by compiled scan nodes.
20#[derive(Debug, Clone, Default)]
21pub struct ResidentGraphSchemaCatalog {
22    by_relation: HashMap<RelId, Vec<(String, Schema)>>,
23}
24
25impl ResidentGraphSchemaCatalog {
26    /// Builds a catalog from compiler-assigned names, relation ids, and schemas.
27    pub fn from_named_schemas(entries: impl IntoIterator<Item = (String, RelId, Schema)>) -> Self {
28        let mut by_relation: HashMap<RelId, Vec<(String, Schema)>> = HashMap::new();
29        for (name, relation, schema) in entries {
30            by_relation
31                .entry(relation)
32                .or_default()
33                .push((name, schema));
34        }
35        for aliases in by_relation.values_mut() {
36            aliases.sort_by(|left, right| left.0.cmp(&right.0));
37            aliases.dedup();
38        }
39        Self { by_relation }
40    }
41
42    fn descriptor(&self, relation: RelId) -> Option<String> {
43        let aliases = self.by_relation.get(&relation)?;
44        Some(
45            aliases
46                .iter()
47                .map(|(name, schema)| format!("{name}={schema:#?}"))
48                .collect::<Vec<_>>()
49                .join("|"),
50        )
51    }
52
53    pub(crate) fn schema(&self, relation: RelId) -> Option<&Schema> {
54        let aliases = self.by_relation.get(&relation)?;
55        let (_, first) = aliases.first()?;
56        aliases
57            .iter()
58            .all(|(_, schema)| schema == first)
59            .then_some(first)
60    }
61}
62
63/// A reason why a complete plan cannot use the resident conditional graph.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum ResidentGraphDeclineReason {
66    /// A scan relation has no compiler schema identity.
67    MissingScanSchema {
68        /// Relation whose scan lacks a compiler schema identity.
69        relation: RelId,
70    },
71    /// A physical node has no resident implementation.
72    UnsupportedNode {
73        /// Stable path to the unsupported node within the physical plan.
74        path: String,
75        /// Physical node kind that has no resident implementation.
76        node: &'static str,
77    },
78    /// The resident route supports only inner and semi joins.
79    UnsupportedJoin {
80        /// Stable path to the unsupported join within the physical plan.
81        path: String,
82        /// Join semantics that the resident route cannot execute.
83        join_type: JoinType,
84    },
85    /// The caller requested a complete relation store, which cannot be staged
86    /// as a query-only transaction.
87    FullStoreRequested,
88    /// The compiled program uses epistemic or another non-ordinary semantics.
89    NonOrdinaryPlan,
90    /// A caller input is imported or belongs to a different memory manager, so
91    /// its lifetime cannot be bound to this resident transaction.
92    ImportedInputUnsupported {
93        /// Imported relation whose ownership cannot join the transaction.
94        relation: String,
95    },
96    /// A scanned source lacks a current provider-produced full-row set proof.
97    SourceSetUncertified {
98        /// Source relation without a current full-row set certificate.
99        relation: String,
100    },
101    /// The CUDA driver cannot construct a conditional WHILE graph.
102    ConditionalGraphUnavailable {
103        /// Driver capability failure reported during conditional-graph setup.
104        detail: String,
105    },
106    /// Setup cannot reserve a bounded workspace without exceeding the budget.
107    WorkspaceUnbounded {
108        /// Workspace bound or reservation failure reported during preflight.
109        detail: String,
110    },
111}
112
113/// Complete prelaunch proof of the physical routes selected for a plan.
114#[derive(Debug, Clone)]
115pub struct ResidentGraphRouteCertificate {
116    covered_route_descriptors: BTreeSet<String>,
117    declines: Vec<ResidentGraphDeclineReason>,
118    schema_catalog: ResidentGraphSchemaCatalog,
119    plan_fingerprint: u64,
120}
121
122/// A route certificate sealed to the exact immutable plan allocation it inspected.
123///
124/// Preparation through this value cannot accidentally pair a certificate with a
125/// different plan. The owned [`Arc`] also prevents safe mutation of the plan while
126/// the seal remains live.
127#[derive(Debug, Clone)]
128pub struct ResidentGraphCertifiedPlan {
129    plan: Arc<ExecutionPlan>,
130    certificate: ResidentGraphRouteCertificate,
131}
132
133fn append_resident_expression_descriptor(descriptor: &mut String, expression: &Expr) {
134    fn schedule_binary<'a>(pending: &mut Vec<&'a Expr>, left: &'a Expr, right: &'a Expr) {
135        pending.push(right);
136        pending.push(left);
137    }
138
139    let mut pending = vec![expression];
140    while let Some(expression) = pending.pop() {
141        match expression {
142            Expr::Column(index) => write!(descriptor, "column:{index};").unwrap(),
143            Expr::Const(value) => {
144                descriptor.push_str("constant:");
145                match value {
146                    ConstValue::U32(value) => write!(descriptor, "u32:{value};").unwrap(),
147                    ConstValue::U64(value) => write!(descriptor, "u64:{value};").unwrap(),
148                    ConstValue::I32(value) => write!(descriptor, "i32:{value};").unwrap(),
149                    ConstValue::I64(value) => write!(descriptor, "i64:{value};").unwrap(),
150                    ConstValue::F32(value) => {
151                        write!(descriptor, "f32:{:08x};", value.to_bits()).unwrap()
152                    }
153                    ConstValue::F64(value) => {
154                        write!(descriptor, "f64:{:016x};", value.to_bits()).unwrap()
155                    }
156                    ConstValue::Bool(value) => write!(descriptor, "bool:{value};").unwrap(),
157                    ConstValue::Symbol(value) => {
158                        write!(descriptor, "symbol:{}:", value.len()).unwrap();
159                        descriptor.push_str(value);
160                        descriptor.push(';');
161                    }
162                }
163            }
164            Expr::Compare { left, op, right } => {
165                write!(descriptor, "compare:{op:?};").unwrap();
166                schedule_binary(&mut pending, left, right);
167            }
168            Expr::And(items) => {
169                write!(descriptor, "and:{};", items.len()).unwrap();
170                pending.extend(items.iter().rev());
171            }
172            Expr::Or(items) => {
173                write!(descriptor, "or:{};", items.len()).unwrap();
174                pending.extend(items.iter().rev());
175            }
176            Expr::Not(inner) => {
177                descriptor.push_str("not;");
178                pending.push(inner);
179            }
180            Expr::Add(left, right) => {
181                descriptor.push_str("add;");
182                schedule_binary(&mut pending, left, right);
183            }
184            Expr::Sub(left, right) => {
185                descriptor.push_str("subtract;");
186                schedule_binary(&mut pending, left, right);
187            }
188            Expr::Mul(left, right) => {
189                descriptor.push_str("multiply;");
190                schedule_binary(&mut pending, left, right);
191            }
192            Expr::Div(left, right) => {
193                descriptor.push_str("divide;");
194                schedule_binary(&mut pending, left, right);
195            }
196            Expr::Mod(left, right) => {
197                descriptor.push_str("modulo;");
198                schedule_binary(&mut pending, left, right);
199            }
200            Expr::Abs(inner) => {
201                descriptor.push_str("absolute_value;");
202                pending.push(inner);
203            }
204            Expr::Min(left, right) => {
205                descriptor.push_str("minimum;");
206                schedule_binary(&mut pending, left, right);
207            }
208            Expr::Max(left, right) => {
209                descriptor.push_str("maximum;");
210                schedule_binary(&mut pending, left, right);
211            }
212            Expr::Pow(left, right) => {
213                descriptor.push_str("power;");
214                schedule_binary(&mut pending, left, right);
215            }
216            Expr::Cast(inner, target) => {
217                write!(descriptor, "cast:{target:?};").unwrap();
218                pending.push(inner);
219            }
220            Expr::Conditional {
221                condition,
222                then_expr,
223                else_expr,
224            } => {
225                descriptor.push_str("conditional;");
226                pending.push(else_expr);
227                pending.push(then_expr);
228                pending.push(condition);
229            }
230        }
231    }
232}
233
234fn resident_project_columns_descriptor(columns: &[ProjectExpr]) -> String {
235    let mut descriptor = format!("column_count={};", columns.len());
236    for column in columns {
237        match column {
238            ProjectExpr::Column(index) => write!(descriptor, "column:{index};").unwrap(),
239            ProjectExpr::Computed(expression, scalar_type) => {
240                write!(descriptor, "computed:{scalar_type:?};").unwrap();
241                append_resident_expression_descriptor(&mut descriptor, expression);
242            }
243        }
244    }
245    descriptor
246}
247
248fn resident_node_descriptor(node: &RirNode) -> String {
249    match node {
250        RirNode::Unit => "unit".to_owned(),
251        RirNode::Scan { rel } => format!("scan;relation={}", rel.0),
252        RirNode::Filter { predicate, .. } => {
253            let mut descriptor = "filter;predicate=".to_owned();
254            append_resident_expression_descriptor(&mut descriptor, predicate);
255            descriptor
256        }
257        RirNode::Project { columns, .. } => {
258            format!("project;{}", resident_project_columns_descriptor(columns))
259        }
260        RirNode::Join {
261            left_keys,
262            right_keys,
263            join_type,
264            ..
265        } => format!(
266            "join;left_keys={left_keys:?};right_keys={right_keys:?};join_type={join_type:?}"
267        ),
268        RirNode::ChainJoin {
269            left_key,
270            right_key,
271            output_columns,
272            ..
273        } => format!(
274            "chain_join;left_key={left_key};right_key={right_key};output_{}",
275            resident_project_columns_descriptor(output_columns)
276        ),
277        RirNode::GroupBy { key_cols, aggs, .. } => {
278            format!("group_by;key_cols={key_cols:?};aggregates={aggs:?}")
279        }
280        RirNode::Union { inputs } => format!("union;input_count={}", inputs.len()),
281        RirNode::Distinct { key_cols, .. } => format!("distinct;key_cols={key_cols:?}"),
282        RirNode::Diff { .. } => "difference".to_owned(),
283        RirNode::Fixpoint {
284            scc_id,
285            delta_rel,
286            full_rel,
287            ..
288        } => format!(
289            "fixpoint;scc_id={scc_id};delta_relation={};full_relation={}",
290            delta_rel.0, full_rel.0
291        ),
292        RirNode::MultiWayJoin {
293            inputs,
294            slot_vars,
295            output_columns,
296            plan,
297            var_order,
298            ..
299        } => format!(
300            "multi_way_join;input_count={};slot_variables={slot_vars:?};output_{};plan={plan:?};variable_order={var_order:?}",
301            inputs.len(),
302            resident_project_columns_descriptor(output_columns)
303        ),
304        RirNode::TensorMaskedJoin {
305            mask_name,
306            schema_size,
307            left_keys,
308            right_keys,
309            rel_index,
310            head_rel_name,
311            head_rel_id,
312            max_active_rules,
313            head_projection,
314        } => format!(
315            "tensor_masked_join;mask={mask_name:?};schema_size={schema_size};left_keys={left_keys:?};right_keys={right_keys:?};relation_index={rel_index:?};head_relation_name={head_rel_name:?};head_relation_id={};max_active_rules={max_active_rules};head_projection={head_projection:?}",
316            head_rel_id.0
317        ),
318    }
319}
320
321impl ResidentGraphCertifiedPlan {
322    /// Inspect and seal one immutable plan allocation.
323    pub fn inspect(plan: Arc<ExecutionPlan>, catalog: &ResidentGraphSchemaCatalog) -> Result<Self> {
324        let certificate = ResidentGraphRouteCertificate::inspect(&plan, catalog)?;
325        Ok(Self { plan, certificate })
326    }
327
328    /// Return the exact plan inspected by this seal.
329    pub fn plan(&self) -> &ExecutionPlan {
330        &self.plan
331    }
332
333    /// Return the certificate produced for the sealed plan.
334    pub fn certificate(&self) -> &ResidentGraphRouteCertificate {
335        &self.certificate
336    }
337}
338
339#[cfg(all(test, feature = "resident-graph-tests"))]
340thread_local! {
341    static RESIDENT_ROUTE_INSPECTION_COUNT: Cell<usize> = const { Cell::new(0) };
342}
343
344#[cfg(all(test, feature = "resident-graph-tests"))]
345pub(crate) fn reset_resident_route_inspection_count() {
346    RESIDENT_ROUTE_INSPECTION_COUNT.with(|count| count.set(0));
347}
348
349#[cfg(all(test, feature = "resident-graph-tests"))]
350pub(crate) fn resident_route_inspection_count() -> usize {
351    RESIDENT_ROUTE_INSPECTION_COUNT.with(Cell::get)
352}
353
354impl ResidentGraphRouteCertificate {
355    /// Inspects every explicit and implicit route in deterministic plan order.
356    pub fn inspect(plan: &ExecutionPlan, catalog: &ResidentGraphSchemaCatalog) -> Result<Self> {
357        #[cfg(all(test, feature = "resident-graph-tests"))]
358        RESIDENT_ROUTE_INSPECTION_COUNT.with(|count| count.set(count.get() + 1));
359        let mut certificate = Self {
360            covered_route_descriptors: BTreeSet::new(),
361            declines: Vec::new(),
362            schema_catalog: catalog.clone(),
363            plan_fingerprint: 0,
364        };
365        certificate
366            .covered_route_descriptors
367            .insert(format!("plan;scc_count={}", plan.sccs.len()));
368        for (scc_index, scc) in plan.sccs.iter().enumerate() {
369            certificate.covered_route_descriptors.insert(format!(
370                "plan;scc={scc_index};id={};recursive={};predicate_count={}",
371                scc.id,
372                scc.is_recursive,
373                scc.predicates.len()
374            ));
375            for (predicate_index, predicate) in scc.predicates.iter().enumerate() {
376                certificate.covered_route_descriptors.insert(format!(
377                    "plan;scc={scc_index};predicate={predicate_index};name={predicate:?}"
378                ));
379            }
380        }
381        certificate
382            .covered_route_descriptors
383            .insert(format!("plan;stratum_count={}", plan.strata.len()));
384        for (stratum_index, stratum) in plan.strata.iter().enumerate() {
385            certificate.covered_route_descriptors.insert(format!(
386                "plan;stratum={stratum_index};id={};scc_count={}",
387                stratum.id,
388                stratum.sccs.len()
389            ));
390            for (scc_position, scc_id) in stratum.sccs.iter().enumerate() {
391                certificate.covered_route_descriptors.insert(format!(
392                    "plan;stratum={stratum_index};scc={scc_position};id={scc_id}"
393                ));
394            }
395        }
396        certificate.covered_route_descriptors.insert(format!(
397            "plan;rules_by_scc_count={}",
398            plan.rules_by_scc.len()
399        ));
400        certificate.covered_route_descriptors.insert(format!(
401            "plan;generated_query_rule_count={}",
402            plan.generated_query_rules.len()
403        ));
404        for (position, provenance) in plan.generated_query_rules.iter().enumerate() {
405            certificate.covered_route_descriptors.insert(format!(
406                "plan;generated_query_rule={position};query_index={};scc={};rule={}",
407                provenance.query_index, provenance.scc_index, provenance.rule_index
408            ));
409        }
410        for (scc_index, rules) in plan.rules_by_scc.iter().enumerate() {
411            certificate.covered_route_descriptors.insert(format!(
412                "plan;rules_by_scc={scc_index};rule_count={}",
413                rules.len()
414            ));
415            for (rule_index, rule) in rules.iter().enumerate() {
416                certificate.covered_route_descriptors.insert(format!(
417                    "plan;rules_by_scc={scc_index};rule={rule_index};head={:?};meta={:?}",
418                    rule.head, rule.meta
419                ));
420            }
421        }
422        certificate
423            .covered_route_descriptors
424            .insert(format!("plan;est_memory_peak={}", plan.est_memory_peak));
425        certificate
426            .covered_route_descriptors
427            .insert(format!("plan;rel_arity_count={}", plan.rel_arities.len()));
428        let mut rel_arities = plan
429            .rel_arities
430            .iter()
431            .map(|(&relation, &arity)| (relation, arity))
432            .collect::<Vec<_>>();
433        rel_arities.sort_unstable_by_key(|(relation, _)| *relation);
434        for (position, (relation, arity)) in rel_arities.into_iter().enumerate() {
435            certificate.covered_route_descriptors.insert(format!(
436                "plan;rel_arity={position};relation={};arity={arity}",
437                relation.0
438            ));
439        }
440        for (scc_index, scc) in plan.sccs.iter().enumerate() {
441            let Some(rules) = plan.rules_by_scc.get(scc_index) else {
442                certificate
443                    .declines
444                    .push(ResidentGraphDeclineReason::UnsupportedNode {
445                        path: format!("scc={scc_index}"),
446                        node: "missing_rule_vector",
447                    });
448                continue;
449            };
450            for (rule_index, rule) in rules.iter().enumerate() {
451                certificate.visit(
452                    catalog,
453                    scc_index,
454                    rule_index,
455                    scc.is_recursive,
456                    &rule.body,
457                    "primary/root",
458                );
459                let identity = format!(
460                    "scc={scc_index};rule={rule_index};head={};schema={:#?}",
461                    rule.head, rule.meta.schema
462                );
463                certificate
464                    .covered_route_descriptors
465                    .insert(format!("{identity};implicit=rule_result_union"));
466                certificate
467                    .covered_route_descriptors
468                    .insert(format!("{identity};implicit=full_row_dedup"));
469                if scc.is_recursive {
470                    certificate
471                        .covered_route_descriptors
472                        .insert(format!("{identity};implicit=novel_tuple_difference"));
473                    certificate
474                        .covered_route_descriptors
475                        .insert(format!("{identity};implicit=device_convergence"));
476                }
477            }
478        }
479        certificate.plan_fingerprint = stable_descriptor_fingerprint(
480            certificate
481                .covered_route_descriptors
482                .iter()
483                .map(String::as_bytes),
484        );
485        Ok(certificate)
486    }
487
488    /// Whether every inspected route has a resident implementation.
489    pub fn is_supported(&self) -> bool {
490        self.declines.is_empty()
491    }
492
493    /// Deterministically ordered route occurrences covered by this proof.
494    pub fn covered_route_descriptors(&self) -> &BTreeSet<String> {
495        &self.covered_route_descriptors
496    }
497
498    /// Fail-closed reasons collected during inspection.
499    pub fn declines(&self) -> &[ResidentGraphDeclineReason] {
500        &self.declines
501    }
502
503    /// Stable binding between this certificate and every inspected physical
504    /// route, expression, key position, schema, and implicit set operation.
505    pub fn plan_fingerprint(&self) -> u64 {
506        self.plan_fingerprint
507    }
508
509    /// Re-inspects a plan and proves that it is the exact plan certified by
510    /// this value. Preparation performs this check before workspace allocation.
511    pub fn matches_plan(&self, plan: &ExecutionPlan) -> Result<bool> {
512        let inspected = Self::inspect(plan, &self.schema_catalog)?;
513        Ok(inspected.plan_fingerprint == self.plan_fingerprint
514            && inspected.covered_route_descriptors == self.covered_route_descriptors
515            && inspected.declines == self.declines)
516    }
517
518    pub(crate) fn schema_for(&self, relation: RelId) -> Option<&Schema> {
519        self.schema_catalog.schema(relation)
520    }
521
522    pub(crate) fn node_schema(&self, node: &RirNode) -> Option<Schema> {
523        node_schema(&self.schema_catalog, node)
524    }
525
526    fn visit(
527        &mut self,
528        catalog: &ResidentGraphSchemaCatalog,
529        scc_index: usize,
530        rule_index: usize,
531        recursive: bool,
532        node: &RirNode,
533        path: &str,
534    ) {
535        self.validate_node_route(catalog, node, path);
536        let scan_schema = match node {
537            RirNode::Scan { rel } => match catalog.descriptor(*rel) {
538                Some(descriptor) => descriptor,
539                None => {
540                    self.declines
541                        .push(ResidentGraphDeclineReason::MissingScanSchema { relation: *rel });
542                    String::new()
543                }
544            },
545            _ => String::new(),
546        };
547        let node_descriptor = resident_node_descriptor(node);
548        self.covered_route_descriptors.insert(format!(
549            "scc={scc_index};rule={rule_index};recursive={recursive};path={path};node={node_descriptor};scan_schema={scan_schema}"
550        ));
551        match node {
552            RirNode::Unit | RirNode::Scan { .. } => {}
553            RirNode::Filter { input, .. }
554            | RirNode::Project { input, .. }
555            | RirNode::Distinct { input, .. } => self.visit(
556                catalog,
557                scc_index,
558                rule_index,
559                recursive,
560                input,
561                &format!("{path}/input"),
562            ),
563            RirNode::Join {
564                left,
565                right,
566                join_type,
567                ..
568            } => {
569                if !matches!(join_type, JoinType::Inner | JoinType::Semi) {
570                    self.declines
571                        .push(ResidentGraphDeclineReason::UnsupportedJoin {
572                            path: path.to_string(),
573                            join_type: *join_type,
574                        });
575                }
576                self.visit(
577                    catalog,
578                    scc_index,
579                    rule_index,
580                    recursive,
581                    left,
582                    &format!("{path}/left"),
583                );
584                self.visit(
585                    catalog,
586                    scc_index,
587                    rule_index,
588                    recursive,
589                    right,
590                    &format!("{path}/right"),
591                );
592            }
593            RirNode::ChainJoin {
594                left,
595                right,
596                fallback,
597                ..
598            } => {
599                self.visit(
600                    catalog,
601                    scc_index,
602                    rule_index,
603                    recursive,
604                    left,
605                    &format!("{path}/primary/left"),
606                );
607                self.visit(
608                    catalog,
609                    scc_index,
610                    rule_index,
611                    recursive,
612                    right,
613                    &format!("{path}/primary/right"),
614                );
615                self.visit(
616                    catalog,
617                    scc_index,
618                    rule_index,
619                    recursive,
620                    fallback,
621                    &format!("{path}/alternative/captured_fallback"),
622                );
623            }
624            RirNode::Union { inputs } => {
625                for (index, input) in inputs.iter().enumerate() {
626                    self.visit(
627                        catalog,
628                        scc_index,
629                        rule_index,
630                        recursive,
631                        input,
632                        &format!("{path}/input[{index}]"),
633                    );
634                }
635            }
636            RirNode::Diff { left, right } => {
637                self.visit(
638                    catalog,
639                    scc_index,
640                    rule_index,
641                    recursive,
642                    left,
643                    &format!("{path}/left"),
644                );
645                self.visit(
646                    catalog,
647                    scc_index,
648                    rule_index,
649                    recursive,
650                    right,
651                    &format!("{path}/right"),
652                );
653            }
654            RirNode::Fixpoint {
655                base,
656                recursive: step,
657                ..
658            } => {
659                self.visit(
660                    catalog,
661                    scc_index,
662                    rule_index,
663                    recursive,
664                    base,
665                    &format!("{path}/base"),
666                );
667                self.visit(
668                    catalog,
669                    scc_index,
670                    rule_index,
671                    recursive,
672                    step,
673                    &format!("{path}/recursive"),
674                );
675            }
676            RirNode::MultiWayJoin {
677                inputs, fallback, ..
678            } => {
679                self.declines
680                    .push(ResidentGraphDeclineReason::UnsupportedNode {
681                        path: path.to_string(),
682                        node: "multi_way_join",
683                    });
684                for (index, input) in inputs.iter().enumerate() {
685                    self.visit(
686                        catalog,
687                        scc_index,
688                        rule_index,
689                        recursive,
690                        input,
691                        &format!("{path}/primary/input[{index}]"),
692                    );
693                }
694                self.visit(
695                    catalog,
696                    scc_index,
697                    rule_index,
698                    recursive,
699                    fallback,
700                    &format!("{path}/alternative/captured_fallback"),
701                );
702            }
703            RirNode::GroupBy { input, .. } => {
704                self.declines
705                    .push(ResidentGraphDeclineReason::UnsupportedNode {
706                        path: path.to_string(),
707                        node: "group_by",
708                    });
709                self.visit(
710                    catalog,
711                    scc_index,
712                    rule_index,
713                    recursive,
714                    input,
715                    &format!("{path}/input"),
716                );
717            }
718            RirNode::TensorMaskedJoin { .. } => {
719                self.declines
720                    .push(ResidentGraphDeclineReason::UnsupportedNode {
721                        path: path.to_string(),
722                        node: "tensor_masked_join",
723                    })
724            }
725        }
726    }
727
728    fn validate_node_route(
729        &mut self,
730        catalog: &ResidentGraphSchemaCatalog,
731        node: &RirNode,
732        path: &str,
733    ) {
734        let schema = node_schema(catalog, node);
735        let unsupported = schema
736            .as_ref()
737            .is_some_and(|schema| schema.arity() > RESIDENT_GRAPH_MAX_INTERMEDIATE_ARITY)
738            .then_some("intermediate_arity")
739            .or_else(|| {
740                schema
741                    .as_ref()
742                    .is_some_and(|schema| {
743                        schema
744                            .columns
745                            .iter()
746                            .any(|(_, scalar)| !resident_scalar_supported(*scalar))
747                    })
748                    .then_some("scalar_type")
749            })
750            .or_else(|| match node {
751                RirNode::Filter { input, predicate } => node_schema(catalog, input)
752                    .filter(|schema| resident_predicate_supported(predicate, schema))
753                    .is_none()
754                    .then_some("filter_expression"),
755                RirNode::Project { input, columns } => node_schema(catalog, input)
756                    .filter(|schema| resident_projection_supported(columns, schema))
757                    .is_none()
758                    .then_some("project_expression"),
759                RirNode::Join {
760                    left,
761                    right,
762                    left_keys,
763                    right_keys,
764                    join_type: JoinType::Inner | JoinType::Semi,
765                } => {
766                    let compatible = (|| {
767                        let left_schema = node_schema(catalog, left)?;
768                        let right_schema = node_schema(catalog, right)?;
769                        if left_keys.len() != 1 || right_keys.len() != 1 {
770                            return None;
771                        }
772                        let left_ty = left_schema.column_type(left_keys[0])?;
773                        let right_ty = right_schema.column_type(right_keys[0])?;
774                        (left_ty == right_ty && resident_scalar_supported(left_ty)).then_some(())
775                    })()
776                    .is_some();
777                    (!compatible).then_some("join_key_layout")
778                }
779                RirNode::ChainJoin {
780                    left,
781                    right,
782                    left_key,
783                    right_key,
784                    output_columns,
785                    ..
786                } => {
787                    let compatible = (|| {
788                        let left_schema = node_schema(catalog, left)?;
789                        let right_schema = node_schema(catalog, right)?;
790                        let left_ty = left_schema.column_type(*left_key)?;
791                        let right_ty = right_schema.column_type(*right_key)?;
792                        if left_ty != right_ty || !resident_scalar_supported(left_ty) {
793                            return None;
794                        }
795                        let joined = joined_schema(&left_schema, &right_schema);
796                        resident_projection_supported(output_columns, &joined).then_some(())
797                    })()
798                    .is_some();
799                    (!compatible).then_some("chain_join_layout")
800                }
801                RirNode::Distinct { input, key_cols } => node_schema(catalog, input)
802                    .filter(|schema| key_cols.iter().copied().eq(0..schema.arity()))
803                    .is_none()
804                    .then_some("partial_row_distinct"),
805                _ => None,
806            });
807        if let Some(node) = unsupported {
808            self.declines
809                .push(ResidentGraphDeclineReason::UnsupportedNode {
810                    path: path.to_string(),
811                    node,
812                });
813        }
814    }
815}
816
817fn resident_scalar_supported(scalar: ScalarType) -> bool {
818    matches!(
819        scalar,
820        ScalarType::Symbol | ScalarType::U32 | ScalarType::U64
821    )
822}
823
824fn constant_scalar(value: &ConstValue) -> ScalarType {
825    match value {
826        ConstValue::U32(_) => ScalarType::U32,
827        ConstValue::U64(_) => ScalarType::U64,
828        ConstValue::I32(_) => ScalarType::I32,
829        ConstValue::I64(_) => ScalarType::I64,
830        ConstValue::F32(_) => ScalarType::F32,
831        ConstValue::F64(_) => ScalarType::F64,
832        ConstValue::Bool(_) => ScalarType::Bool,
833        ConstValue::Symbol(_) => ScalarType::Symbol,
834    }
835}
836
837fn scalar_expression_type(expression: &Expr, schema: &Schema) -> Option<ScalarType> {
838    match expression {
839        Expr::Column(index) => schema.column_type(*index),
840        Expr::Const(value) => Some(constant_scalar(value)),
841        _ => None,
842    }
843}
844
845fn resident_predicate_supported(expression: &Expr, schema: &Schema) -> bool {
846    match expression {
847        Expr::Compare { left, right, .. } => {
848            let Some(left_ty) = scalar_expression_type(left, schema) else {
849                return false;
850            };
851            let Some(right_ty) = scalar_expression_type(right, schema) else {
852                return false;
853            };
854            left_ty == right_ty && resident_scalar_supported(left_ty)
855        }
856        Expr::And(expressions) => {
857            !expressions.is_empty()
858                && expressions
859                    .iter()
860                    .all(|expression| resident_predicate_supported(expression, schema))
861        }
862        _ => false,
863    }
864}
865
866fn resident_projection_supported(expressions: &[ProjectExpr], schema: &Schema) -> bool {
867    expressions.iter().all(|expression| match expression {
868        ProjectExpr::Column(index) => schema
869            .column_type(*index)
870            .is_some_and(resident_scalar_supported),
871        ProjectExpr::Computed(Expr::Const(value), declared) => {
872            *declared == constant_scalar(value) && resident_scalar_supported(*declared)
873        }
874        _ => false,
875    })
876}
877
878fn joined_schema(left: &Schema, right: &Schema) -> Schema {
879    Schema::new(
880        left.columns
881            .iter()
882            .chain(&right.columns)
883            .enumerate()
884            .map(|(index, (_, ty))| (format!("column_{index}"), *ty))
885            .collect(),
886    )
887}
888
889pub(crate) fn node_schema(catalog: &ResidentGraphSchemaCatalog, node: &RirNode) -> Option<Schema> {
890    match node {
891        RirNode::Unit => Some(Schema::new(Vec::new())),
892        RirNode::Scan { rel } => catalog.schema(*rel).cloned(),
893        RirNode::Filter { input, .. } | RirNode::Distinct { input, .. } => {
894            node_schema(catalog, input)
895        }
896        RirNode::Project { input, columns } => {
897            let input = node_schema(catalog, input)?;
898            let output = columns
899                .iter()
900                .enumerate()
901                .map(|(index, expression)| {
902                    let scalar = match expression {
903                        ProjectExpr::Column(column) => input.column_type(*column),
904                        ProjectExpr::Computed(_, scalar) => Some(*scalar),
905                    };
906                    scalar.map(|scalar| (format!("column_{index}"), scalar))
907                })
908                .collect::<Option<Vec<_>>>()?;
909            Some(Schema::new(output))
910        }
911        RirNode::Join {
912            left,
913            right,
914            join_type,
915            ..
916        } => {
917            let left = node_schema(catalog, left)?;
918            if matches!(join_type, JoinType::Semi | JoinType::Anti) {
919                Some(left)
920            } else {
921                Some(joined_schema(&left, &node_schema(catalog, right)?))
922            }
923        }
924        RirNode::ChainJoin { fallback, .. } | RirNode::MultiWayJoin { fallback, .. } => {
925            node_schema(catalog, fallback)
926        }
927        RirNode::Union { inputs } => inputs.first().and_then(|input| node_schema(catalog, input)),
928        RirNode::Diff { left, .. } => node_schema(catalog, left),
929        RirNode::Fixpoint { base, .. } => node_schema(catalog, base),
930        RirNode::GroupBy { .. } | RirNode::TensorMaskedJoin { .. } => None,
931    }
932}
933
934fn stable_descriptor_fingerprint<'a>(descriptors: impl IntoIterator<Item = &'a [u8]>) -> u64 {
935    let mut hash = 0xcbf2_9ce4_8422_2325u64;
936    for descriptor in descriptors {
937        for byte in (descriptor.len() as u64)
938            .to_le_bytes()
939            .iter()
940            .chain(descriptor)
941        {
942            hash ^= u64::from(*byte);
943            hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
944        }
945    }
946    hash
947}
948
949/// Device-written terminal status for a resident graph transaction.
950#[derive(Debug, Clone, PartialEq, Eq)]
951pub enum ResidentGraphDeviceStatus {
952    /// The graph converged and staged output is valid.
953    Success {
954        /// Device-observed recursive iterations completed before convergence.
955        iterations: u32,
956    },
957    /// The exact configured iteration limit was exhausted.
958    IterationLimit {
959        /// Configured maximum recursive iterations.
960        limit: u32,
961        /// Iterations completed when the device stopped the graph.
962        completed: u32,
963    },
964    /// An operator's exact output exceeded its reserved row capacity.
965    CapacityOverflow {
966        /// Physical operator that exceeded its reserved row count.
967        op_id: u32,
968        /// Exact row count required by the operator.
969        required: u64,
970        /// Reserved row capacity available to the operator.
971        capacity: u64,
972    },
973    /// A bounded device resource was insufficient.
974    ResourceExhausted {
975        /// Physical operator that exhausted the bounded resource.
976        op_id: u32,
977        /// Stable name of the exhausted device resource.
978        resource: &'static str,
979        /// Exact resource quantity required by the operator.
980        required: u64,
981        /// Reserved resource quantity available to the operator.
982        capacity: u64,
983    },
984}
985
986/// Typed error decoded from the graph's single terminal receipt.
987#[derive(Debug, Clone, PartialEq, Eq)]
988pub enum ResidentGraphExecutionError {
989    /// A complete prelaunch inspection selected the existing GPU route instead.
990    Declined(ResidentGraphDeclineReason),
991    /// The exact configured iteration limit was exhausted.
992    IterationLimit {
993        /// Configured maximum recursive iterations.
994        limit: u32,
995        /// Iterations completed before the device reported exhaustion.
996        completed: u32,
997    },
998    /// An operator's exact output exceeded its reserved row capacity.
999    CapacityOverflow {
1000        /// Physical operator that exceeded its reserved row count.
1001        op_id: u32,
1002        /// Exact row count required by the operator.
1003        required: u64,
1004        /// Reserved row capacity available to the operator.
1005        capacity: u64,
1006    },
1007    /// A bounded device resource was insufficient.
1008    ResourceExhausted {
1009        /// Physical operator that exhausted the bounded resource.
1010        op_id: u32,
1011        /// Stable name of the exhausted device resource.
1012        resource: &'static str,
1013        /// Exact resource quantity required by the operator.
1014        required: u64,
1015        /// Reserved resource quantity available to the operator.
1016        capacity: u64,
1017    },
1018    /// Setup or execution failed before a valid terminal status existed.
1019    Runtime(String),
1020}
1021
1022impl fmt::Display for ResidentGraphExecutionError {
1023    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1024        write!(formatter, "{self:?}")
1025    }
1026}
1027
1028impl std::error::Error for ResidentGraphExecutionError {}
1029
1030/// Test-only device-kernel status request.  Production execution never writes
1031/// terminal status from the host.
1032#[derive(Debug, Clone)]
1033pub struct ResidentGraphDeviceStatusTestInjection {
1034    pub(crate) after_op: u32,
1035    pub(crate) status: ResidentGraphDeviceStatus,
1036}
1037
1038impl ResidentGraphDeviceStatusTestInjection {
1039    /// Requests a device status-writer kernel after the indexed physical op.
1040    pub fn device_kernel_after_op(after_op: u32, status: ResidentGraphDeviceStatus) -> Self {
1041        Self { after_op, status }
1042    }
1043}
1044
1045/// Prelaunch options for a resident transaction.
1046#[derive(Debug, Clone, Default)]
1047pub struct ResidentGraphPrepareOptions {
1048    pub(crate) test_device_status: Option<ResidentGraphDeviceStatusTestInjection>,
1049    pub(crate) latency_diagnostic_sample: Option<u64>,
1050}
1051
1052impl ResidentGraphPrepareOptions {
1053    /// Adds a device-written test status without permitting host injection.
1054    pub fn with_test_device_status(
1055        mut self,
1056        injection: ResidentGraphDeviceStatusTestInjection,
1057    ) -> Self {
1058        self.test_device_status = Some(injection);
1059        self
1060    }
1061
1062    /// Enables cold-path prepare timing for one explicitly numbered diagnostic sample.
1063    #[doc(hidden)]
1064    pub fn with_latency_diagnostic_sample(mut self, sample: u64) -> Self {
1065        self.latency_diagnostic_sample = Some(sample);
1066        self
1067    }
1068}
1069
1070/// Copyable, opt-in cold-path timings captured before a resident graph launches.
1071#[doc(hidden)]
1072#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1073pub struct ResidentGraphPrepareDiagnosticSnapshot {
1074    pub(crate) sample: u64,
1075    pub(crate) total_ns: u64,
1076    pub(crate) admission_and_source_snapshot_ns: u64,
1077    pub(crate) execution_domain_and_build_setup_ns: u64,
1078    pub(crate) logical_schedule_planning_ns: u64,
1079    pub(crate) manifest_compact_construction_ns: u64,
1080    pub(crate) schedule_lowering_ns: u64,
1081    pub(crate) reservation_ns: u64,
1082    pub(crate) relation_preparation_ns: u64,
1083    pub(crate) count_initialization_ns: u64,
1084    pub(crate) workspace_preparation_ns: u64,
1085    pub(crate) metadata_binding_construction_ns: u64,
1086    pub(crate) metadata_preparation_ns: u64,
1087    pub(crate) reservation_validation_and_release_ns: u64,
1088    pub(crate) pinned_receipt_ns: u64,
1089    pub(crate) graph_body_capture_ns: u64,
1090    pub(crate) graph_instantiate_ns: u64,
1091    pub(crate) validation_owner_assembly_ns: u64,
1092    pub(crate) unattributed_ns: u64,
1093    pub(crate) required_reservation_bytes: u64,
1094    pub(crate) logical_relation_values: u64,
1095    pub(crate) physical_relation_slots: u64,
1096    pub(crate) relation_device_allocation_calls: u64,
1097    pub(crate) relation_reserved_bytes: u64,
1098    pub(crate) relation_slot_preparation_ns_max: u64,
1099    pub(crate) count_memset_calls: u64,
1100    pub(crate) count_memset_bytes: u64,
1101    pub(crate) count_initialization_ns_max: u64,
1102    pub(crate) workspace_provider_calls: u64,
1103    pub(crate) workspace_reserved_bytes: u64,
1104    pub(crate) filter_scratch_preparation_ns: u64,
1105    pub(crate) filter_scratch_reserved_bytes: u64,
1106    pub(crate) set_workspace_preparation_ns: u64,
1107    pub(crate) set_workspace_reserved_bytes: u64,
1108    pub(crate) join_workspace_preparation_ns: u64,
1109    pub(crate) join_workspace_reserved_bytes: u64,
1110    pub(crate) control_preparation_ns: u64,
1111    pub(crate) control_reserved_bytes: u64,
1112    pub(crate) metadata_provider_calls: u64,
1113    pub(crate) metadata_reserved_bytes: u64,
1114    pub(crate) metadata_initial_htod_calls: u64,
1115    pub(crate) metadata_initial_htod_bytes: u64,
1116    pub(crate) device_trace_preparation_ns: u64,
1117    pub(crate) device_trace_reserved_bytes: u64,
1118    pub(crate) device_trace_initial_htod_calls: u64,
1119    pub(crate) device_trace_initial_htod_bytes: u64,
1120    pub(crate) schema_winners_preparation_ns: u64,
1121    pub(crate) schema_winners_reserved_bytes: u64,
1122    pub(crate) schema_winners_initial_htod_calls: u64,
1123    pub(crate) schema_winners_initial_htod_bytes: u64,
1124    pub(crate) receipt_preparation_ns: u64,
1125    pub(crate) receipt_reserved_bytes: u64,
1126    pub(crate) receipt_initial_htod_calls: u64,
1127    pub(crate) receipt_initial_htod_bytes: u64,
1128    pub(crate) schedule_program_preparation_ns: u64,
1129    pub(crate) schedule_program_reserved_bytes: u64,
1130    pub(crate) schedule_program_initial_htod_calls: u64,
1131    pub(crate) schedule_program_initial_htod_bytes: u64,
1132    pub(crate) compact_ops: u64,
1133    pub(crate) compact_waves: u64,
1134    pub(crate) compact_regions: u64,
1135    pub(crate) conditional_regions: u64,
1136    pub(crate) parent_graph_nodes: u64,
1137    pub(crate) conditional_body_nodes: u64,
1138}
1139
1140impl ResidentGraphPrepareDiagnosticSnapshot {
1141    /// Format one stable, machine-tokenizable diagnostic line without emitting it.
1142    #[doc(hidden)]
1143    pub fn format_line(self) -> String {
1144        let Self {
1145            sample,
1146            total_ns,
1147            admission_and_source_snapshot_ns,
1148            execution_domain_and_build_setup_ns,
1149            logical_schedule_planning_ns,
1150            manifest_compact_construction_ns,
1151            schedule_lowering_ns,
1152            reservation_ns,
1153            relation_preparation_ns,
1154            count_initialization_ns,
1155            workspace_preparation_ns,
1156            metadata_binding_construction_ns,
1157            metadata_preparation_ns,
1158            reservation_validation_and_release_ns,
1159            pinned_receipt_ns,
1160            graph_body_capture_ns,
1161            graph_instantiate_ns,
1162            validation_owner_assembly_ns,
1163            unattributed_ns,
1164            required_reservation_bytes,
1165            logical_relation_values,
1166            physical_relation_slots,
1167            relation_device_allocation_calls,
1168            relation_reserved_bytes,
1169            relation_slot_preparation_ns_max,
1170            count_memset_calls,
1171            count_memset_bytes,
1172            count_initialization_ns_max,
1173            workspace_provider_calls,
1174            workspace_reserved_bytes,
1175            filter_scratch_preparation_ns,
1176            filter_scratch_reserved_bytes,
1177            set_workspace_preparation_ns,
1178            set_workspace_reserved_bytes,
1179            join_workspace_preparation_ns,
1180            join_workspace_reserved_bytes,
1181            control_preparation_ns,
1182            control_reserved_bytes,
1183            metadata_provider_calls,
1184            metadata_reserved_bytes,
1185            metadata_initial_htod_calls,
1186            metadata_initial_htod_bytes,
1187            device_trace_preparation_ns,
1188            device_trace_reserved_bytes,
1189            device_trace_initial_htod_calls,
1190            device_trace_initial_htod_bytes,
1191            schema_winners_preparation_ns,
1192            schema_winners_reserved_bytes,
1193            schema_winners_initial_htod_calls,
1194            schema_winners_initial_htod_bytes,
1195            receipt_preparation_ns,
1196            receipt_reserved_bytes,
1197            receipt_initial_htod_calls,
1198            receipt_initial_htod_bytes,
1199            schedule_program_preparation_ns,
1200            schedule_program_reserved_bytes,
1201            schedule_program_initial_htod_calls,
1202            schedule_program_initial_htod_bytes,
1203            compact_ops,
1204            compact_waves,
1205            compact_regions,
1206            conditional_regions,
1207            parent_graph_nodes,
1208            conditional_body_nodes,
1209        } = self;
1210        format!(
1211            "resident prepare phases: sample={sample} total_ns={total_ns} admission_and_source_snapshot_ns={admission_and_source_snapshot_ns} execution_domain_and_build_setup_ns={execution_domain_and_build_setup_ns} logical_schedule_planning_ns={logical_schedule_planning_ns} manifest_compact_construction_ns={manifest_compact_construction_ns} schedule_lowering_ns={schedule_lowering_ns} reservation_ns={reservation_ns} relation_preparation_ns={relation_preparation_ns} count_initialization_ns={count_initialization_ns} workspace_preparation_ns={workspace_preparation_ns} metadata_binding_construction_ns={metadata_binding_construction_ns} metadata_preparation_ns={metadata_preparation_ns} reservation_validation_and_release_ns={reservation_validation_and_release_ns} pinned_receipt_ns={pinned_receipt_ns} graph_body_capture_ns={graph_body_capture_ns} graph_instantiate_ns={graph_instantiate_ns} validation_owner_assembly_ns={validation_owner_assembly_ns} unattributed_ns={unattributed_ns} required_reservation_bytes={required_reservation_bytes} logical_relation_values={logical_relation_values} physical_relation_slots={physical_relation_slots} relation_device_allocation_calls={relation_device_allocation_calls} relation_reserved_bytes={relation_reserved_bytes} relation_slot_preparation_ns_max={relation_slot_preparation_ns_max} count_memset_calls={count_memset_calls} count_memset_bytes={count_memset_bytes} count_initialization_ns_max={count_initialization_ns_max} workspace_provider_calls={workspace_provider_calls} workspace_reserved_bytes={workspace_reserved_bytes} filter_scratch_preparation_ns={filter_scratch_preparation_ns} filter_scratch_reserved_bytes={filter_scratch_reserved_bytes} set_workspace_preparation_ns={set_workspace_preparation_ns} set_workspace_reserved_bytes={set_workspace_reserved_bytes} join_workspace_preparation_ns={join_workspace_preparation_ns} join_workspace_reserved_bytes={join_workspace_reserved_bytes} control_preparation_ns={control_preparation_ns} control_reserved_bytes={control_reserved_bytes} metadata_provider_calls={metadata_provider_calls} metadata_reserved_bytes={metadata_reserved_bytes} metadata_initial_htod_calls={metadata_initial_htod_calls} metadata_initial_htod_bytes={metadata_initial_htod_bytes} device_trace_preparation_ns={device_trace_preparation_ns} device_trace_reserved_bytes={device_trace_reserved_bytes} device_trace_initial_htod_calls={device_trace_initial_htod_calls} device_trace_initial_htod_bytes={device_trace_initial_htod_bytes} schema_winners_preparation_ns={schema_winners_preparation_ns} schema_winners_reserved_bytes={schema_winners_reserved_bytes} schema_winners_initial_htod_calls={schema_winners_initial_htod_calls} schema_winners_initial_htod_bytes={schema_winners_initial_htod_bytes} receipt_preparation_ns={receipt_preparation_ns} receipt_reserved_bytes={receipt_reserved_bytes} receipt_initial_htod_calls={receipt_initial_htod_calls} receipt_initial_htod_bytes={receipt_initial_htod_bytes} schedule_program_preparation_ns={schedule_program_preparation_ns} schedule_program_reserved_bytes={schedule_program_reserved_bytes} schedule_program_initial_htod_calls={schedule_program_initial_htod_calls} schedule_program_initial_htod_bytes={schedule_program_initial_htod_bytes} compact_ops={compact_ops} compact_waves={compact_waves} compact_regions={compact_regions} conditional_regions={conditional_regions} parent_graph_nodes={parent_graph_nodes} conditional_body_nodes={conditional_body_nodes} deallocation_calls=unavailable"
1212        )
1213    }
1214}
1215
1216#[cfg(test)]
1217fn resident_graph_prepare_diagnostic_line(
1218    snapshot: Option<ResidentGraphPrepareDiagnosticSnapshot>,
1219) -> Option<String> {
1220    snapshot.map(ResidentGraphPrepareDiagnosticSnapshot::format_line)
1221}
1222
1223/// Runtime route selected for one ordinary evaluation.
1224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1225pub enum ResidentGraphSelectionKind {
1226    /// The existing host-dispatched GPU executor ran the plan.
1227    ExistingGpu,
1228    /// A device-controlled conditional CUDA graph ran the plan.
1229    ResidentConditionalGraph,
1230}
1231
1232/// Core-loop host transfer counters.
1233#[derive(Debug, Clone, Default)]
1234pub struct ResidentGraphCoreTransferStats {
1235    /// Host-to-device calls observed by the tracked memory runtime.
1236    pub tracked_htod_calls: u64,
1237    /// Host-to-device bytes observed by the tracked memory runtime.
1238    pub tracked_htod_bytes: u64,
1239    /// Device-to-host calls observed by the tracked memory runtime.
1240    pub tracked_dtoh_calls: u64,
1241    /// Device-to-host bytes observed by the tracked memory runtime.
1242    pub tracked_dtoh_bytes: u64,
1243    /// Device-to-host calls issued through provider metadata operations.
1244    pub provider_dtoh_calls: u64,
1245    /// Metadata device-to-host calls not attributable to the tracked runtime.
1246    pub untracked_metadata_dtoh_calls: u64,
1247}
1248
1249/// The one bounded observation after the terminal synchronization.
1250#[derive(Debug, Clone, Default)]
1251pub struct ResidentGraphFinalObservationStats {
1252    /// Device-to-host calls used to read the terminal observation.
1253    pub dtoh_calls: u64,
1254    /// Device-to-host bytes used to read the terminal observation.
1255    pub dtoh_bytes: u64,
1256    /// Pinned host receipts used for the terminal observation.
1257    pub pinned_receipts: u64,
1258}
1259
1260/// CUDA-event timing resolved after the graph completes.
1261#[derive(Debug, Clone, Default)]
1262pub struct ResidentGraphDeferredProfile {
1263    /// Resident scan and filter invocations covered by deferred CUDA timing.
1264    pub timed_scan_filter_invocations: u64,
1265    /// Device elapsed time resolved from CUDA events after completion.
1266    pub device_elapsed_ns: u64,
1267    /// Host synchronization time excluded from device execution timing.
1268    pub final_sync_misattributed_ns: u64,
1269}
1270
1271/// Truthful telemetry for resident selection, execution, and decline.
1272#[derive(Debug, Clone)]
1273pub struct ResidentGraphExecutionStats {
1274    /// Runtime route selected for the evaluation.
1275    pub selection: ResidentGraphSelectionKind,
1276    /// Preflight decline reason when the existing GPU route was selected.
1277    pub decline: Option<ResidentGraphDeclineReason>,
1278    /// Conditional resident graph launches performed by this evaluation.
1279    pub conditional_graph_launches: u64,
1280    /// Host synchronizations performed to obtain terminal status.
1281    pub terminal_synchronizations: u64,
1282    /// Fixpoint iterations controlled by the host during the core loop.
1283    pub host_iterations: u64,
1284    /// Host allocations performed during the resident core loop.
1285    pub host_allocations: u64,
1286    /// Terminal statuses injected by the host rather than written by a device kernel.
1287    pub host_status_injections: u64,
1288    /// Deterministic device-to-host transfer contract violations.
1289    pub deterministic_d2h_violations: u64,
1290    /// Scan operations dispatched individually by the host.
1291    pub host_dispatched_scan_ops: u64,
1292    /// Filter operations dispatched individually by the host.
1293    pub host_dispatched_filter_ops: u64,
1294    /// Physical Scan nodes executed by the resident device graph.
1295    pub device_scan_invocations: u64,
1296    /// Physical Filter nodes executed by the resident device graph.
1297    pub device_filter_invocations: u64,
1298    /// Logical Scan count for the selected dependency-closed plan after
1299    /// excluding recursive variants whose input delta was empty.
1300    pub semantic_scan_invocations: u64,
1301    /// Logical Filter count for the selected dependency-closed plan after
1302    /// excluding recursive variants whose input delta was empty.
1303    pub semantic_filter_invocations: u64,
1304    /// Relation-store mutations staged until terminal success is authoritative.
1305    pub staged_store_mutations: u64,
1306    /// CUDA-event measurements resolved after the graph completes.
1307    pub deferred_profile: ResidentGraphDeferredProfile,
1308    /// Host/device transfers observed inside the resident core loop.
1309    pub core_transfers: ResidentGraphCoreTransferStats,
1310    /// Single bounded terminal observation made after synchronization.
1311    pub final_observation: ResidentGraphFinalObservationStats,
1312}
1313
1314impl ResidentGraphExecutionStats {
1315    /// Telemetry for a call that deliberately remained on the existing GPU path.
1316    pub fn declined(reason: ResidentGraphDeclineReason) -> Self {
1317        Self {
1318            selection: ResidentGraphSelectionKind::ExistingGpu,
1319            decline: Some(reason),
1320            conditional_graph_launches: 0,
1321            terminal_synchronizations: 0,
1322            host_iterations: 0,
1323            host_allocations: 0,
1324            host_status_injections: 0,
1325            deterministic_d2h_violations: 0,
1326            host_dispatched_scan_ops: 0,
1327            host_dispatched_filter_ops: 0,
1328            device_scan_invocations: 0,
1329            device_filter_invocations: 0,
1330            semantic_scan_invocations: 0,
1331            semantic_filter_invocations: 0,
1332            staged_store_mutations: 0,
1333            deferred_profile: ResidentGraphDeferredProfile::default(),
1334            core_transfers: ResidentGraphCoreTransferStats::default(),
1335            final_observation: ResidentGraphFinalObservationStats::default(),
1336        }
1337    }
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342    use super::*;
1343    use std::sync::Arc;
1344    use xlog_ir::{CompiledRule, GeneratedQueryRuleProvenance, RirMeta, Scc, Stratum};
1345
1346    fn schema(columns: &[(&str, ScalarType)]) -> Schema {
1347        Schema::new(
1348            columns
1349                .iter()
1350                .map(|(name, scalar)| ((*name).to_owned(), *scalar))
1351                .collect(),
1352        )
1353    }
1354
1355    fn rule(head: &str, body: RirNode, columns: &[(&str, ScalarType)]) -> CompiledRule {
1356        CompiledRule {
1357            head: head.to_owned(),
1358            body,
1359            meta: RirMeta {
1360                schema: schema(columns),
1361                ..RirMeta::default()
1362            },
1363        }
1364    }
1365
1366    fn chain_join() -> RirNode {
1367        RirNode::ChainJoin {
1368            left: Box::new(RirNode::Scan { rel: RelId(1) }),
1369            right: Box::new(RirNode::Scan { rel: RelId(1) }),
1370            left_key: 0,
1371            right_key: 0,
1372            output_columns: vec![ProjectExpr::Column(0)],
1373            fallback: Box::new(RirNode::Join {
1374                left: Box::new(RirNode::Scan { rel: RelId(1) }),
1375                right: Box::new(RirNode::Scan { rel: RelId(1) }),
1376                left_keys: vec![0],
1377                right_keys: vec![0],
1378                join_type: JoinType::Inner,
1379            }),
1380        }
1381    }
1382
1383    fn representative_plan() -> ExecutionPlan {
1384        let mut rel_arities = HashMap::new();
1385        rel_arities.insert(RelId(9), 0);
1386        rel_arities.insert(RelId(1), 2);
1387        ExecutionPlan {
1388            sccs: vec![
1389                Scc {
1390                    id: 10,
1391                    predicates: vec!["alpha".to_owned(), "beta".to_owned()],
1392                    is_recursive: true,
1393                },
1394                Scc {
1395                    id: 20,
1396                    predicates: Vec::new(),
1397                    is_recursive: false,
1398                },
1399                Scc {
1400                    id: 30,
1401                    predicates: vec!["gamma".to_owned()],
1402                    is_recursive: false,
1403                },
1404                Scc {
1405                    id: 40,
1406                    predicates: Vec::new(),
1407                    is_recursive: false,
1408                },
1409            ],
1410            strata: vec![
1411                Stratum {
1412                    id: 0,
1413                    sccs: vec![10],
1414                },
1415                Stratum {
1416                    id: 1,
1417                    sccs: Vec::new(),
1418                },
1419                Stratum {
1420                    id: 2,
1421                    sccs: vec![20, 30, 40],
1422                },
1423            ],
1424            rules_by_scc: vec![
1425                vec![
1426                    rule(
1427                        "alpha",
1428                        RirNode::Scan { rel: RelId(1) },
1429                        &[("left", ScalarType::U32), ("right", ScalarType::Symbol)],
1430                    ),
1431                    rule("beta", chain_join(), &[("value", ScalarType::U64)]),
1432                ],
1433                Vec::new(),
1434                vec![rule("gamma", RirNode::Unit, &[])],
1435                Vec::new(),
1436            ],
1437            generated_query_rules: vec![],
1438            est_memory_peak: 4_096,
1439            rel_arities,
1440        }
1441    }
1442
1443    fn catalog() -> ResidentGraphSchemaCatalog {
1444        ResidentGraphSchemaCatalog::from_named_schemas([(
1445            "input".to_owned(),
1446            RelId(1),
1447            schema(&[("left", ScalarType::U32), ("right", ScalarType::Symbol)]),
1448        )])
1449    }
1450
1451    fn assert_mutation_rejected(mutate: impl FnOnce(&mut ExecutionPlan)) {
1452        let plan = representative_plan();
1453        let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1454        let mut mutated = plan.clone();
1455        mutate(&mut mutated);
1456        assert!(!certificate.matches_plan(&mutated).unwrap());
1457    }
1458
1459    fn assert_generated_query_provenance_mutation_rejected(
1460        mutate: impl FnOnce(&mut GeneratedQueryRuleProvenance),
1461    ) {
1462        let mut plan = representative_plan();
1463        plan.sccs[0].predicates[0] = "__xlog_query_0".to_owned();
1464        plan.rules_by_scc[0][0].head = "__xlog_query_0".to_owned();
1465        plan.generated_query_rules = vec![GeneratedQueryRuleProvenance {
1466            query_index: 0,
1467            scc_index: 0,
1468            rule_index: 0,
1469        }];
1470        let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1471        mutate(&mut plan.generated_query_rules[0]);
1472        assert!(!certificate.matches_plan(&plan).unwrap());
1473    }
1474
1475    #[test]
1476    fn resident_node_descriptors_encode_only_local_fields() {
1477        let descendant = RirNode::Project {
1478            input: Box::new(RirNode::Scan { rel: RelId(9) }),
1479            columns: vec![ProjectExpr::Column(0)],
1480        };
1481        let project = RirNode::Project {
1482            input: Box::new(descendant),
1483            columns: vec![ProjectExpr::Computed(
1484                Expr::Const(ConstValue::U32(7)),
1485                ScalarType::U32,
1486            )],
1487        };
1488
1489        let descriptor = resident_node_descriptor(&project);
1490        assert_eq!(
1491            descriptor,
1492            "project;column_count=1;computed:U32;constant:u32:7;"
1493        );
1494        assert!(!descriptor.contains("Scan"));
1495        assert!(!descriptor.contains("input"));
1496    }
1497
1498    #[test]
1499    fn resident_node_descriptors_distinguish_local_semantics() {
1500        let left = resident_node_descriptor(&RirNode::Join {
1501            left: Box::new(RirNode::Unit),
1502            right: Box::new(RirNode::Unit),
1503            left_keys: vec![0],
1504            right_keys: vec![1],
1505            join_type: JoinType::Inner,
1506        });
1507        let right = resident_node_descriptor(&RirNode::Join {
1508            left: Box::new(RirNode::Unit),
1509            right: Box::new(RirNode::Unit),
1510            left_keys: vec![1],
1511            right_keys: vec![0],
1512            join_type: JoinType::Inner,
1513        });
1514        assert_ne!(left, right);
1515
1516        let negative_zero = resident_node_descriptor(&RirNode::Filter {
1517            input: Box::new(RirNode::Unit),
1518            predicate: Expr::Const(ConstValue::F64(-0.0)),
1519        });
1520        let positive_zero = resident_node_descriptor(&RirNode::Filter {
1521            input: Box::new(RirNode::Unit),
1522            predicate: Expr::Const(ConstValue::F64(0.0)),
1523        });
1524        assert_ne!(negative_zero, positive_zero);
1525    }
1526
1527    #[test]
1528    fn certificate_descriptors_remain_bounded_for_deep_unary_plans() {
1529        let mut body = RirNode::Scan { rel: RelId(1) };
1530        for _ in 0..128 {
1531            body = RirNode::Project {
1532                input: Box::new(body),
1533                columns: vec![ProjectExpr::Column(0)],
1534            };
1535        }
1536        let mut plan = representative_plan();
1537        plan.rules_by_scc[0][0].body = body;
1538
1539        let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1540        let largest_descriptor = certificate
1541            .covered_route_descriptors
1542            .iter()
1543            .map(String::len)
1544            .max()
1545            .unwrap();
1546        assert!(largest_descriptor < 8_192, "{largest_descriptor}");
1547    }
1548
1549    #[test]
1550    fn certified_plan_seals_the_exact_arc_and_certificate() {
1551        let plan = Arc::new(representative_plan());
1552        let certified = ResidentGraphCertifiedPlan::inspect(Arc::clone(&plan), &catalog()).unwrap();
1553
1554        assert!(std::ptr::eq(Arc::as_ptr(&plan), certified.plan()));
1555        assert!(certified
1556            .certificate()
1557            .matches_plan(certified.plan())
1558            .unwrap());
1559
1560        let mut independent_mutation = (*plan).clone();
1561        independent_mutation.est_memory_peak += 1;
1562        assert!(!certified
1563            .certificate()
1564            .matches_plan(&independent_mutation)
1565            .unwrap());
1566    }
1567
1568    #[test]
1569    fn prepare_latency_diagnostics_are_explicit_and_sample_bound() {
1570        let disabled = ResidentGraphPrepareOptions::default();
1571        assert_eq!(disabled.latency_diagnostic_sample, None);
1572
1573        let enabled = disabled.with_latency_diagnostic_sample(7);
1574        assert_eq!(enabled.latency_diagnostic_sample, Some(7));
1575    }
1576
1577    #[test]
1578    fn prepare_latency_diagnostic_format_is_aligned_and_default_off_is_silent() {
1579        let mut snapshot = ResidentGraphPrepareDiagnosticSnapshot {
1580            sample: 17,
1581            total_ns: 101,
1582            admission_and_source_snapshot_ns: 102,
1583            execution_domain_and_build_setup_ns: 109,
1584            metadata_binding_construction_ns: 110,
1585            reservation_validation_and_release_ns: 111,
1586            relation_device_allocation_calls: 103,
1587            workspace_provider_calls: 104,
1588            metadata_initial_htod_calls: 105,
1589            metadata_initial_htod_bytes: 106,
1590            graph_instantiate_ns: 107,
1591            unattributed_ns: 108,
1592            ..ResidentGraphPrepareDiagnosticSnapshot::default()
1593        };
1594        snapshot.schema_winners_initial_htod_calls = 1;
1595        snapshot.schema_winners_initial_htod_bytes = 12;
1596
1597        let line = resident_graph_prepare_diagnostic_line(Some(snapshot))
1598            .expect("enabled diagnostics produce one line");
1599        for expected in [
1600            "sample=17",
1601            "total_ns=101",
1602            "admission_and_source_snapshot_ns=102",
1603            "execution_domain_and_build_setup_ns=109",
1604            "metadata_binding_construction_ns=110",
1605            "reservation_validation_and_release_ns=111",
1606            "relation_device_allocation_calls=103",
1607            "workspace_provider_calls=104",
1608            "metadata_initial_htod_calls=105",
1609            "metadata_initial_htod_bytes=106",
1610            "schema_winners_initial_htod_calls=1",
1611            "schema_winners_initial_htod_bytes=12",
1612            "graph_instantiate_ns=107",
1613            "unattributed_ns=108",
1614        ] {
1615            assert!(line.split_ascii_whitespace().any(|field| field == expected));
1616        }
1617        assert_eq!(line.matches("sample=17").count(), 1);
1618        assert!(!line.contains("admission_source_validation_ns="));
1619        assert_eq!(resident_graph_prepare_diagnostic_line(None), None);
1620    }
1621
1622    #[test]
1623    fn certificate_binds_strata_vector_length() {
1624        assert_mutation_rejected(|plan| {
1625            plan.strata.push(Stratum {
1626                id: 3,
1627                sccs: Vec::new(),
1628            });
1629        });
1630    }
1631
1632    #[test]
1633    fn certificate_binds_empty_stratum_position() {
1634        assert_mutation_rejected(|plan| plan.strata.swap(1, 2));
1635    }
1636
1637    #[test]
1638    fn certificate_binds_stratum_id() {
1639        assert_mutation_rejected(|plan| plan.strata[1].id = 99);
1640    }
1641
1642    #[test]
1643    fn certificate_binds_ordered_stratum_scc_membership() {
1644        assert_mutation_rejected(|plan| plan.strata[2].sccs.swap(0, 1));
1645    }
1646
1647    #[test]
1648    fn certificate_binds_scc_vector_length_including_empty_sccs() {
1649        assert_mutation_rejected(|plan| {
1650            plan.sccs.pop();
1651        });
1652    }
1653
1654    #[test]
1655    fn certificate_binds_scc_id() {
1656        assert_mutation_rejected(|plan| plan.sccs[1].id = 99);
1657    }
1658
1659    #[test]
1660    fn certificate_binds_ordered_scc_predicate_membership() {
1661        assert_mutation_rejected(|plan| plan.sccs[0].predicates.swap(0, 1));
1662    }
1663
1664    #[test]
1665    fn certificate_binds_scc_recursive_flag() {
1666        assert_mutation_rejected(|plan| plan.sccs[1].is_recursive = true);
1667    }
1668
1669    #[test]
1670    fn certificate_binds_rules_by_scc_vector_length() {
1671        assert_mutation_rejected(|plan| plan.rules_by_scc.push(Vec::new()));
1672    }
1673
1674    #[test]
1675    fn certificate_binds_rule_occurrence_count() {
1676        assert_mutation_rejected(|plan| {
1677            plan.rules_by_scc[0].pop();
1678        });
1679    }
1680
1681    #[test]
1682    fn certificate_binds_rule_order() {
1683        assert_mutation_rejected(|plan| plan.rules_by_scc[0].swap(0, 1));
1684    }
1685
1686    #[test]
1687    fn certificate_binds_generated_query_provenance_omission() {
1688        let mut plan = representative_plan();
1689        plan.generated_query_rules = vec![GeneratedQueryRuleProvenance {
1690            query_index: 0,
1691            scc_index: 0,
1692            rule_index: 0,
1693        }];
1694        let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1695        plan.generated_query_rules.clear();
1696        assert!(!certificate.matches_plan(&plan).unwrap());
1697    }
1698
1699    #[test]
1700    fn certificate_binds_generated_query_index() {
1701        assert_generated_query_provenance_mutation_rejected(|provenance| {
1702            provenance.query_index = 1;
1703        });
1704    }
1705
1706    #[test]
1707    fn certificate_binds_generated_query_rule_position() {
1708        assert_generated_query_provenance_mutation_rejected(|provenance| {
1709            provenance.rule_index = 1;
1710        });
1711    }
1712
1713    #[test]
1714    fn certificate_binds_rule_schema_type() {
1715        assert_mutation_rejected(|plan| {
1716            plan.rules_by_scc[0][0].meta.schema =
1717                schema(&[("left", ScalarType::U64), ("right", ScalarType::Symbol)]);
1718        });
1719    }
1720
1721    #[test]
1722    fn certificate_binds_rule_schema_arity() {
1723        assert_mutation_rejected(|plan| {
1724            plan.rules_by_scc[0][0].meta.schema = schema(&[("left", ScalarType::U32)]);
1725        });
1726    }
1727
1728    #[test]
1729    fn certificate_binds_primary_rir() {
1730        assert_mutation_rejected(|plan| plan.rules_by_scc[0][0].body = RirNode::Unit);
1731    }
1732
1733    #[test]
1734    fn certificate_binds_captured_alternative_rir() {
1735        assert_mutation_rejected(|plan| {
1736            let RirNode::ChainJoin { fallback, .. } = &mut plan.rules_by_scc[0][1].body else {
1737                panic!("representative rule must carry a captured alternative");
1738            };
1739            **fallback = RirNode::Unit;
1740        });
1741    }
1742
1743    #[test]
1744    fn certificate_binds_implicit_device_convergence_route() {
1745        let plan = representative_plan();
1746        let mut certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1747        let convergence = certificate
1748            .covered_route_descriptors
1749            .iter()
1750            .find(|descriptor| {
1751                descriptor.contains("scc=0;rule=0;head=alpha;")
1752                    && descriptor.ends_with(";implicit=device_convergence")
1753            })
1754            .cloned()
1755            .expect("recursive rule must certify implicit device convergence");
1756        assert!(certificate.covered_route_descriptors.remove(&convergence));
1757        certificate
1758            .covered_route_descriptors
1759            .insert(format!("{convergence};mutated"));
1760        certificate.plan_fingerprint = stable_descriptor_fingerprint(
1761            certificate
1762                .covered_route_descriptors
1763                .iter()
1764                .map(String::as_bytes),
1765        );
1766        assert!(!certificate.matches_plan(&plan).unwrap());
1767    }
1768
1769    #[test]
1770    fn certificate_binds_rel_arities_vector_length() {
1771        assert_mutation_rejected(|plan| {
1772            plan.rel_arities.insert(RelId(7), 3);
1773        });
1774    }
1775
1776    #[test]
1777    fn certificate_binds_relation_arity() {
1778        assert_mutation_rejected(|plan| {
1779            plan.rel_arities.insert(RelId(1), 3);
1780        });
1781    }
1782
1783    #[test]
1784    fn certificate_rel_arities_are_order_independent() {
1785        let plan = representative_plan();
1786        let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog()).unwrap();
1787        let mut reordered = plan.clone();
1788        reordered.rel_arities.clear();
1789        reordered.rel_arities.insert(RelId(1), 2);
1790        reordered.rel_arities.insert(RelId(9), 0);
1791        assert!(certificate.matches_plan(&reordered).unwrap());
1792    }
1793
1794    #[test]
1795    fn certificate_binds_estimated_memory_peak() {
1796        assert_mutation_rejected(|plan| plan.est_memory_peak += 1);
1797    }
1798
1799    #[test]
1800    fn certificate_rejects_intermediate_arity_above_fixed_metadata_envelope() {
1801        let columns = (0..18)
1802            .map(|index| (format!("column_{index}"), ScalarType::U32))
1803            .collect::<Vec<_>>();
1804        let wide_schema = Schema::new(columns);
1805        let plan = ExecutionPlan {
1806            sccs: vec![Scc {
1807                id: 1,
1808                predicates: vec!["wide".to_owned()],
1809                is_recursive: false,
1810            }],
1811            strata: vec![Stratum {
1812                id: 0,
1813                sccs: vec![1],
1814            }],
1815            rules_by_scc: vec![vec![CompiledRule {
1816                head: "wide".to_owned(),
1817                body: RirNode::Scan { rel: RelId(1) },
1818                meta: RirMeta {
1819                    schema: wide_schema.clone(),
1820                    ..RirMeta::default()
1821                },
1822            }]],
1823            generated_query_rules: vec![],
1824            est_memory_peak: 0,
1825            rel_arities: HashMap::from([(RelId(1), 18)]),
1826        };
1827        let catalog = ResidentGraphSchemaCatalog::from_named_schemas([(
1828            "wide".to_owned(),
1829            RelId(1),
1830            wide_schema,
1831        )]);
1832        let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog).unwrap();
1833        assert!(!certificate.is_supported());
1834        assert!(certificate.declines().iter().any(|decline| matches!(
1835            decline,
1836            ResidentGraphDeclineReason::UnsupportedNode {
1837                node: "intermediate_arity",
1838                ..
1839            }
1840        )));
1841    }
1842
1843    #[test]
1844    fn certificate_accepts_intermediate_arity_at_fixed_metadata_envelope() {
1845        let columns = (0..17)
1846            .map(|index| (format!("column_{index}"), ScalarType::U32))
1847            .collect::<Vec<_>>();
1848        let wide_schema = Schema::new(columns);
1849        let plan = ExecutionPlan {
1850            sccs: vec![Scc {
1851                id: 1,
1852                predicates: vec!["wide".to_owned()],
1853                is_recursive: false,
1854            }],
1855            strata: vec![Stratum {
1856                id: 0,
1857                sccs: vec![1],
1858            }],
1859            rules_by_scc: vec![vec![CompiledRule {
1860                head: "wide".to_owned(),
1861                body: RirNode::Scan { rel: RelId(1) },
1862                meta: RirMeta {
1863                    schema: wide_schema.clone(),
1864                    ..RirMeta::default()
1865                },
1866            }]],
1867            generated_query_rules: vec![],
1868            est_memory_peak: 0,
1869            rel_arities: HashMap::from([(RelId(1), 17)]),
1870        };
1871        let catalog = ResidentGraphSchemaCatalog::from_named_schemas([(
1872            "wide".to_owned(),
1873            RelId(1),
1874            wide_schema,
1875        )]);
1876        let certificate = ResidentGraphRouteCertificate::inspect(&plan, &catalog).unwrap();
1877        assert!(certificate.is_supported(), "{:#?}", certificate.declines());
1878    }
1879}