Skip to main content

xlog_cuda/provider/
resident_relational.rs

1//! Fixed-address relational primitives for CUDA conditional graphs.
2//!
3//! Every allocation happens in the `prepare_*` methods. The `record_*` methods
4//! only enqueue fixed-address memset and kernel nodes on the supplied stream,
5//! so they are safe to call while a conditional graph body is being captured.
6
7use std::ffi::c_void;
8use std::sync::Arc;
9
10use cudarc::driver::sys;
11use xlog_core::{Result, ScalarType, Schema, XlogError};
12
13use crate::cuda_compat::{AsKernelParam, DeviceSlice, LaunchAsync, LaunchConfig};
14use crate::launch::LaunchRecorder;
15use crate::memory::{GpuMemoryReservation, TrackedCudaSlice};
16use crate::{CudaBuffer, CudaColumn, CudaStream};
17
18use super::CudaKernelProvider;
19
20const MODULE: &str = "xlog_resident_relational";
21const BLOCK_SIZE: u32 = 256;
22
23/// Maximum relation width accepted by the resident relational wire ABI.
24pub const RESIDENT_RELATIONAL_MAX_ARITY: usize = 17;
25
26/// Stable terminal status codes shared by every resident CUDA primitive.
27#[repr(u32)]
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ResidentTerminalCode {
30    Running = 0,
31    Success = 1,
32    IterationLimit = 2,
33    CapacityOverflow = 3,
34    ResourceExhausted = 4,
35}
36
37/// Stable resource identifiers for [`ResidentTerminalStatus::resource_code`].
38#[repr(u32)]
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum ResidentResourceCode {
41    None = 0,
42    SetHashSlots = 1,
43    JoinBuckets = 2,
44    JoinChains = 3,
45    InputRows = 4,
46    OutputRows = 5,
47}
48
49/// Canonical host/device terminal receipt for resident graph execution.
50///
51/// CUDA reserves `0xffff_fffe` internally while the winning thread publishes
52/// a payload. It writes the public terminal code last after a system fence, so
53/// concurrent failures cannot produce a mixed receipt.
54#[repr(C, align(8))]
55#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
56pub struct ResidentTerminalStatus {
57    pub code: u32,
58    pub op_id: u32,
59    pub resource_code: u32,
60    pub iterations: u32,
61    pub limit: u32,
62    pub reserved: u32,
63    pub required: u64,
64    pub capacity: u64,
65}
66
67// SAFETY: repr(C), no references or padding with validity requirements, and
68// every bit pattern is valid for all fields.
69unsafe impl cudarc::driver::DeviceRepr for ResidentTerminalStatus {}
70
71#[repr(C, align(8))]
72#[derive(Clone, Copy)]
73struct ResidentRelationView {
74    columns: [u64; RESIDENT_RELATIONAL_MAX_ARITY],
75    widths: [u32; RESIDENT_RELATIONAL_MAX_ARITY],
76    arity: u32,
77    capacity: u32,
78    reserved: u32,
79    num_rows: u64,
80}
81
82impl AsKernelParam for ResidentRelationView {
83    fn as_kernel_param(&self) -> *mut c_void {
84        (self as *const Self).cast_mut().cast()
85    }
86}
87
88/// A fixed-capacity relation whose storage remains stable for a graph lifetime.
89pub struct ResidentRelation {
90    buffer: CudaBuffer,
91}
92
93impl ResidentRelation {
94    pub fn buffer(&self) -> &CudaBuffer {
95        &self.buffer
96    }
97
98    /// Consume the stable owner after terminal receipt validation.
99    ///
100    /// This performs no transfer, synchronization, or host-count observation.
101    pub fn into_buffer(self) -> CudaBuffer {
102        self.buffer
103    }
104
105    /// Consume a synchronized final owner with its receipt-selected schema.
106    ///
107    /// This changes metadata only after all captured work is complete and
108    /// validates that the selected schema has the same physical layout.
109    pub fn into_buffer_with_observed_schema(mut self, schema: Schema) -> Result<CudaBuffer> {
110        if schema.arity() != self.buffer.arity() {
111            return Err(XlogError::Kernel(format!(
112                "resident observed schema arity {} does not match allocation arity {}",
113                schema.arity(),
114                self.buffer.arity()
115            )));
116        }
117        for column in 0..schema.arity() {
118            let old_width = width(
119                self.buffer
120                    .schema()
121                    .column_type(column)
122                    .expect("matching arity"),
123            )?;
124            let new_width = width(schema.column_type(column).expect("matching arity"))?;
125            if old_width != new_width {
126                return Err(XlogError::Kernel(format!(
127                    "resident observed schema column {column} changes physical width from \
128                     {old_width} to {new_width} bytes"
129                )));
130            }
131        }
132        self.buffer.set_schema(schema);
133        Ok(self.buffer)
134    }
135
136    /// Retag a cold scratch allocation without changing any device address.
137    ///
138    /// This is only valid before the graph that will use this relation is
139    /// launched. Captured kernel nodes own copies of their pointer/width
140    /// arguments; callers must not retag a relation while a launch is in flight.
141    /// Final head relations must not use this scratch-pool operation.
142    pub fn retag_schema_for_capture(&mut self, schema: Schema) -> Result<()> {
143        if schema.arity() != self.buffer.arity() {
144            return Err(XlogError::Kernel(format!(
145                "resident scratch retag arity {} does not match allocation arity {}",
146                schema.arity(),
147                self.buffer.arity()
148            )));
149        }
150        for column in 0..schema.arity() {
151            let old_width = width(
152                self.buffer
153                    .schema()
154                    .column_type(column)
155                    .expect("matching arity"),
156            )?;
157            let new_width = width(schema.column_type(column).expect("matching arity"))?;
158            if old_width != new_width {
159                return Err(XlogError::Kernel(format!(
160                    "resident scratch retag column {column} changes physical width from \
161                     {old_width} to {new_width} bytes"
162                )));
163            }
164        }
165        self.buffer.set_schema(schema);
166        Ok(())
167    }
168
169    pub fn capacity(&self) -> u32 {
170        self.buffer
171            .num_rows()
172            .try_into()
173            .expect("resident capacity")
174    }
175
176    pub fn num_rows_device(&self) -> &TrackedCudaSlice<u32> {
177        self.buffer.num_rows_device()
178    }
179
180    /// Add every owned allocation to the transaction's strict recorder.
181    pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
182        for column in self.buffer.columns() {
183            recorder.write_column(column);
184        }
185        recorder.read_write(self.buffer.num_rows_device());
186    }
187}
188
189/// Preallocated open-addressed full-row set workspace.
190pub struct ResidentSetWorkspace {
191    slots: TrackedCudaSlice<u64>,
192    required: TrackedCudaSlice<u64>,
193    candidate_capacity: u32,
194    slot_mask: u32,
195}
196
197impl ResidentSetWorkspace {
198    pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
199        recorder.read_write(&self.slots);
200        recorder.read_write(&self.required);
201    }
202
203    pub(crate) fn schedule_parts(&self) -> (u64, u64, u32, u32) {
204        (
205            self.slots.device_ptr_value(),
206            self.required.device_ptr_value(),
207            self.slot_mask,
208            self.candidate_capacity,
209        )
210    }
211
212    pub(crate) fn schedule_owner_snapshots(
213        &self,
214    ) -> Result<[Option<crate::memory::RuntimeAllocationIdentity>; 2]> {
215        Ok([
216            self.slots.runtime_allocation_identity()?,
217            self.required.runtime_allocation_identity()?,
218        ])
219    }
220}
221
222/// Preallocated one-key hash-join workspace.
223pub struct ResidentJoinWorkspace {
224    bucket_heads: TrackedCudaSlice<u32>,
225    next: TrackedCudaSlice<u32>,
226    required: TrackedCudaSlice<u64>,
227    right_capacity: u32,
228    bucket_mask: u32,
229}
230
231impl ResidentJoinWorkspace {
232    pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
233        recorder.read_write(&self.bucket_heads);
234        recorder.read_write(&self.next);
235        recorder.read_write(&self.required);
236    }
237
238    pub(crate) fn schedule_parts(&self) -> (u64, u64, u64, u32, u32) {
239        (
240            self.bucket_heads.device_ptr_value(),
241            self.next.device_ptr_value(),
242            self.required.device_ptr_value(),
243            self.bucket_mask,
244            self.right_capacity,
245        )
246    }
247
248    pub(crate) fn schedule_owner_snapshots(
249        &self,
250    ) -> Result<[Option<crate::memory::RuntimeAllocationIdentity>; 3]> {
251        Ok([
252            self.bucket_heads.runtime_allocation_identity()?,
253            self.next.runtime_allocation_identity()?,
254            self.required.runtime_allocation_identity()?,
255        ])
256    }
257}
258
259/// Owns the single canonical terminal receipt used by a resident graph.
260pub struct ResidentConvergenceControl {
261    status: TrackedCudaSlice<ResidentTerminalStatus>,
262    changed: TrackedCudaSlice<u32>,
263    loop_iterations: TrackedCudaSlice<u32>,
264}
265
266/// Device-owned physical and legacy-semantic resident scan/filter counters.
267pub struct ResidentDeviceTrace {
268    scan_invocations: TrackedCudaSlice<u32>,
269    filter_invocations: TrackedCudaSlice<u32>,
270    semantic_scan_invocations: TrackedCudaSlice<u32>,
271    semantic_filter_invocations: TrackedCudaSlice<u32>,
272}
273
274/// Device-owned first-nonempty schema selection for staged relation heads.
275pub struct ResidentSchemaWinners {
276    seen_nonempty: TrackedCudaSlice<u32>,
277    winner_schema_ids: TrackedCudaSlice<u32>,
278    default_schema_ids: Vec<u32>,
279    len: u32,
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub(crate) enum ResidentReceiptPointeeRole {
284    RelationCount(u32),
285    ScanTrace,
286    FilterTrace,
287    SemanticScanTrace,
288    SemanticFilterTrace,
289    SchemaWinner(u32),
290}
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
293pub(crate) struct ResidentReceiptPointee {
294    pub(crate) role: ResidentReceiptPointeeRole,
295    pub(crate) ptr: u64,
296    pub(crate) range_end: u64,
297    pub(crate) manager_id: usize,
298    pub(crate) block: Option<crate::device_runtime::BlockId>,
299}
300
301fn checked_receipt_pointee(
302    role: ResidentReceiptPointeeRole,
303    ptr: u64,
304    manager_id: usize,
305    runtime_block: Option<(crate::device_runtime::BlockId, usize)>,
306) -> Result<ResidentReceiptPointee> {
307    let range_end = ptr.checked_add(4).ok_or_else(|| {
308        XlogError::Kernel(
309            "resident receipt pointee range overflows the device address space".into(),
310        )
311    })?;
312    let block = match runtime_block {
313        Some((block, bytes)) => {
314            let block_bytes = u64::try_from(bytes).map_err(|_| {
315                XlogError::Kernel("resident receipt runtime block size is not representable".into())
316            })?;
317            let block_end = block.ptr.checked_add(block_bytes).ok_or_else(|| {
318                XlogError::Kernel("resident receipt runtime block range overflows".into())
319            })?;
320            if ptr < block.ptr || range_end > block_end {
321                return Err(XlogError::Kernel(
322                    "resident receipt pointee is outside its runtime block".into(),
323                ));
324            }
325            Some(block)
326        }
327        None => None,
328    };
329    Ok(ResidentReceiptPointee {
330        role,
331        ptr,
332        range_end,
333        manager_id,
334        block,
335    })
336}
337
338fn validate_receipt_pointee_ranges(pointees: &[ResidentReceiptPointee]) -> Result<()> {
339    for (index, pointee) in pointees.iter().enumerate() {
340        for previous in &pointees[..index] {
341            if pointee.ptr < previous.range_end && previous.ptr < pointee.range_end {
342                return Err(XlogError::Kernel(
343                    "resident receipt pointee ranges overlap".into(),
344                ));
345            }
346        }
347    }
348    Ok(())
349}
350
351fn validate_receipt_pointee_owners(
352    pointees: &[ResidentReceiptPointee],
353    manager_id: usize,
354    device_ordinal: u32,
355) -> Result<()> {
356    for pointee in pointees {
357        if pointee.manager_id != manager_id {
358            return Err(XlogError::Kernel(
359                "resident receipt pointee belongs to a foreign memory manager".into(),
360            ));
361        }
362        let block = pointee.block.ok_or_else(|| {
363            XlogError::Kernel("resident receipt pointee has no runtime block identity".into())
364        })?;
365        if block.device_ordinal != device_ordinal {
366            return Err(XlogError::Kernel(
367                "resident receipt pointee belongs to a foreign CUDA device".into(),
368            ));
369        }
370    }
371    Ok(())
372}
373
374fn validate_receipt_schedule_block_mapping(
375    pointees: &[ResidentReceiptPointee],
376    expected_blocks: &[crate::device_runtime::BlockId],
377) -> Result<()> {
378    if pointees.len() != expected_blocks.len()
379        || pointees
380            .iter()
381            .zip(expected_blocks)
382            .any(|(pointee, expected)| pointee.block != Some(*expected))
383    {
384        return Err(XlogError::Kernel(
385            "resident receipt runtime-block mapping differs from the schedule".into(),
386        ));
387    }
388    Ok(())
389}
390
391fn record_receipt_pointee_uses(pointees: &[ResidentReceiptPointee], recorder: &mut LaunchRecorder) {
392    for pointee in pointees {
393        recorder.read_optional_block_identity(pointee.block);
394    }
395}
396
397fn validate_receipt_schedule_mapping(
398    pointees: &[ResidentReceiptPointee],
399    relation_counts: &[u64],
400    trace_counts: [u64; 4],
401    schema_winners: &[u64],
402) -> Result<()> {
403    if relation_counts.len() != schema_winners.len() {
404        return Err(XlogError::Kernel(
405            "resident receipt relation and schema-winner mappings differ in length".into(),
406        ));
407    }
408    let expected_len = relation_counts
409        .len()
410        .checked_mul(2)
411        .and_then(|count| count.checked_add(4))
412        .ok_or_else(|| XlogError::Kernel("resident receipt mapping length overflow".into()))?;
413    if pointees.len() != expected_len {
414        return Err(XlogError::Kernel(
415            "resident receipt pointee manifest has the wrong length".into(),
416        ));
417    }
418    for (index, ptr) in relation_counts.iter().copied().enumerate() {
419        let index = u32::try_from(index)
420            .map_err(|_| XlogError::Kernel("resident receipt head index overflow".into()))?;
421        if pointees[index as usize].role != ResidentReceiptPointeeRole::RelationCount(index)
422            || pointees[index as usize].ptr != ptr
423        {
424            return Err(XlogError::Kernel(
425                "resident receipt relation-count mapping differs from the schedule".into(),
426            ));
427        }
428    }
429    let trace_offset = relation_counts.len();
430    if pointees[trace_offset].role != ResidentReceiptPointeeRole::ScanTrace
431        || pointees[trace_offset].ptr != trace_counts[0]
432        || pointees[trace_offset + 1].role != ResidentReceiptPointeeRole::FilterTrace
433        || pointees[trace_offset + 1].ptr != trace_counts[1]
434        || pointees[trace_offset + 2].role != ResidentReceiptPointeeRole::SemanticScanTrace
435        || pointees[trace_offset + 2].ptr != trace_counts[2]
436        || pointees[trace_offset + 3].role != ResidentReceiptPointeeRole::SemanticFilterTrace
437        || pointees[trace_offset + 3].ptr != trace_counts[3]
438    {
439        return Err(XlogError::Kernel(
440            "resident receipt trace mapping differs from the schedule".into(),
441        ));
442    }
443    let winner_offset = trace_offset + 4;
444    for (index, ptr) in schema_winners.iter().copied().enumerate() {
445        let index_u32 = u32::try_from(index)
446            .map_err(|_| XlogError::Kernel("resident receipt head index overflow".into()))?;
447        let pointee = &pointees[winner_offset + index];
448        if pointee.role != ResidentReceiptPointeeRole::SchemaWinner(index_u32) || pointee.ptr != ptr
449        {
450            return Err(XlogError::Kernel(
451                "resident receipt schema-winner mapping differs from the schedule".into(),
452            ));
453        }
454    }
455    Ok(())
456}
457
458impl ResidentSchemaWinners {
459    /// Add both fixed-address arrays to the transaction's strict recorder.
460    pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
461        recorder.read_write(&self.seen_nonempty);
462        recorder.read_write(&self.winner_schema_ids);
463    }
464
465    pub(crate) fn schedule_parts(&self) -> (u64, u64, u32) {
466        (
467            self.seen_nonempty.device_ptr_value(),
468            self.winner_schema_ids.device_ptr_value(),
469            self.len,
470        )
471    }
472
473    pub(crate) fn default_schema_ids(&self) -> &[u32] {
474        &self.default_schema_ids
475    }
476
477    pub fn len(&self) -> u32 {
478        self.len
479    }
480
481    pub(crate) fn schedule_owner_snapshots(
482        &self,
483    ) -> Result<[Option<crate::memory::RuntimeAllocationIdentity>; 2]> {
484        Ok([
485            self.seen_nonempty.runtime_allocation_identity()?,
486            self.winner_schema_ids.runtime_allocation_identity()?,
487        ])
488    }
489}
490
491impl ResidentDeviceTrace {
492    /// Add all fixed-address counters to the transaction's strict recorder.
493    pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
494        recorder.read_write(&self.scan_invocations);
495        recorder.read_write(&self.filter_invocations);
496        recorder.read_write(&self.semantic_scan_invocations);
497        recorder.read_write(&self.semantic_filter_invocations);
498    }
499
500    pub(crate) fn schedule_parts(&self) -> (u64, u64, u64, u64) {
501        (
502            self.scan_invocations.device_ptr_value(),
503            self.filter_invocations.device_ptr_value(),
504            self.semantic_scan_invocations.device_ptr_value(),
505            self.semantic_filter_invocations.device_ptr_value(),
506        )
507    }
508
509    pub(crate) fn schedule_owner_snapshots(
510        &self,
511    ) -> Result<[Option<crate::memory::RuntimeAllocationIdentity>; 4]> {
512        Ok([
513            self.scan_invocations.runtime_allocation_identity()?,
514            self.filter_invocations.runtime_allocation_identity()?,
515            self.semantic_scan_invocations
516                .runtime_allocation_identity()?,
517            self.semantic_filter_invocations
518                .runtime_allocation_identity()?,
519        ])
520    }
521}
522
523/// One fixed-size device receipt: terminal wire record, changed flag, then u32 counts.
524pub struct ResidentPackedReceipt {
525    count_ptrs: TrackedCudaSlice<u64>,
526    bytes: TrackedCudaSlice<u8>,
527    pointees: Vec<ResidentReceiptPointee>,
528    count_len: u32,
529    relation_count_len: u32,
530    device_trace_field_count: u32,
531    schema_winner_count: u32,
532}
533
534/// Exact-size page-locked destination for the single final resident receipt.
535#[derive(Debug)]
536pub struct ResidentPinnedReceipt {
537    ptr: std::ptr::NonNull<u8>,
538    len: usize,
539}
540
541// SAFETY: the allocation has unique ownership, exposes no host pointer, and
542// CUDA permits page-locked host allocations to be freed from another thread.
543unsafe impl Send for ResidentPinnedReceipt {}
544
545impl ResidentPinnedReceipt {
546    pub fn len_bytes(&self) -> usize {
547        self.len
548    }
549}
550
551impl Drop for ResidentPinnedReceipt {
552    fn drop(&mut self) {
553        // SAFETY: `ptr` was returned by `cuMemHostAlloc` and is freed once here.
554        let _ = unsafe { sys::cuMemFreeHost(self.ptr.as_ptr().cast()) };
555    }
556}
557
558impl ResidentPackedReceipt {
559    pub(crate) fn pointee_manifest(&self) -> &[ResidentReceiptPointee] {
560        &self.pointees
561    }
562
563    pub fn device_bytes(&self) -> &TrackedCudaSlice<u8> {
564        &self.bytes
565    }
566
567    pub fn len_bytes(&self) -> usize {
568        self.bytes.len()
569    }
570
571    pub fn relation_count_len(&self) -> u32 {
572        self.relation_count_len
573    }
574
575    pub fn device_trace_field_count(&self) -> u32 {
576        self.device_trace_field_count
577    }
578
579    pub fn schema_winner_count(&self) -> u32 {
580        self.schema_winner_count
581    }
582
583    pub fn total_count_field_len(&self) -> u32 {
584        self.count_len
585    }
586
587    pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
588        recorder.read(&self.count_ptrs);
589        recorder.write(&self.bytes);
590        record_receipt_pointee_uses(&self.pointees, recorder);
591    }
592
593    pub(crate) fn schedule_parts(&self) -> (u64, u64, u32, u32) {
594        (
595            self.count_ptrs.device_ptr_value(),
596            self.bytes.device_ptr_value(),
597            self.count_len,
598            u32::try_from(self.bytes.len()).unwrap_or(u32::MAX),
599        )
600    }
601
602    pub(crate) fn validate_schedule_pointees(
603        &self,
604        manager_id: usize,
605        device_ordinal: u32,
606        relation_counts: &[u64],
607        trace_counts: [u64; 4],
608        schema_winners: &[u64],
609        expected_blocks: &[crate::device_runtime::BlockId],
610    ) -> Result<()> {
611        validate_receipt_pointee_owners(&self.pointees, manager_id, device_ordinal)?;
612        validate_receipt_schedule_mapping(
613            &self.pointees,
614            relation_counts,
615            trace_counts,
616            schema_winners,
617        )?;
618        validate_receipt_schedule_block_mapping(&self.pointees, expected_blocks)
619    }
620
621    pub(crate) fn schedule_owner_snapshots(
622        &self,
623    ) -> Result<[Option<crate::memory::RuntimeAllocationIdentity>; 2]> {
624        Ok([
625            self.count_ptrs.runtime_allocation_identity()?,
626            self.bytes.runtime_allocation_identity()?,
627        ])
628    }
629}
630
631impl ResidentConvergenceControl {
632    pub fn status_device(&self) -> &TrackedCudaSlice<ResidentTerminalStatus> {
633        &self.status
634    }
635
636    pub fn status_device_ptr(&self) -> u64 {
637        self.status.device_ptr_value()
638    }
639
640    pub fn iterations_device_ptr(&self) -> u64 {
641        self.status.device_ptr_value() + 12
642    }
643
644    pub(crate) fn schedule_owner_snapshots(
645        &self,
646    ) -> Result<[Option<crate::memory::RuntimeAllocationIdentity>; 3]> {
647        Ok([
648            self.status.runtime_allocation_identity()?,
649            self.changed.runtime_allocation_identity()?,
650            self.loop_iterations.runtime_allocation_identity()?,
651        ])
652    }
653
654    pub fn changed_device(&self) -> &TrackedCudaSlice<u32> {
655        &self.changed
656    }
657
658    pub fn changed_device_ptr(&self) -> u64 {
659        self.changed.device_ptr_value()
660    }
661
662    pub fn loop_iterations_device(&self) -> &TrackedCudaSlice<u32> {
663        &self.loop_iterations
664    }
665
666    pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
667        recorder.read_write(&self.status);
668        recorder.read_write(&self.changed);
669        recorder.read_write(&self.loop_iterations);
670    }
671}
672
673/// One-key join kinds required by the resident corpus.
674#[derive(Debug, Clone, Copy, PartialEq, Eq)]
675pub enum ResidentJoinKind {
676    Inner,
677    Semi,
678}
679
680pub(crate) fn checked_hash_slot_capacity(candidate_capacity: u64) -> Result<u64> {
681    let doubled = candidate_capacity
682        .checked_mul(2)
683        .ok_or_else(|| XlogError::Kernel("resident hash capacity overflow".to_string()))?;
684    let slots = doubled
685        .max(1)
686        .checked_next_power_of_two()
687        .ok_or_else(|| XlogError::Kernel("resident hash capacity overflow".to_string()))?;
688    if slots > u64::from(u32::MAX) + 1 {
689        return Err(XlogError::Kernel(format!(
690            "resident hash requires {slots} slots, exceeding the u32 index space"
691        )));
692    }
693    Ok(slots)
694}
695
696fn checked_capacity(capacity: u64, label: &str) -> Result<u32> {
697    u32::try_from(capacity).map_err(|_| {
698        XlogError::Kernel(format!(
699            "resident {label} capacity {capacity} exceeds u32::MAX"
700        ))
701    })
702}
703
704/// Exact manager-tracked bytes for one fixed-capacity resident relation.
705pub fn resident_relation_device_bytes(schema: &Schema, capacity: u64) -> Result<u64> {
706    checked_capacity(capacity, "relation")?;
707    capacity
708        .checked_mul(schema.row_size_bytes() as u64)
709        .and_then(|bytes| bytes.checked_add(std::mem::size_of::<u32>() as u64))
710        .ok_or_else(|| XlogError::Kernel("resident relation byte overflow".into()))
711}
712
713/// Exact manager-tracked bytes for the shared full-row set workspace.
714pub fn resident_set_workspace_device_bytes(candidate_capacity: u64) -> Result<u64> {
715    let slots = checked_hash_slot_capacity(candidate_capacity)?;
716    slots
717        .checked_mul(std::mem::size_of::<u64>() as u64)
718        .and_then(|bytes| bytes.checked_add(std::mem::size_of::<u64>() as u64))
719        .ok_or_else(|| XlogError::Kernel("resident set workspace byte overflow".into()))
720}
721
722/// Exact manager-tracked bytes for the shared one-key join workspace.
723pub fn resident_join_workspace_device_bytes(right_capacity: u64) -> Result<u64> {
724    let buckets = checked_hash_slot_capacity(right_capacity)?;
725    buckets
726        .checked_mul(std::mem::size_of::<u32>() as u64)
727        .and_then(|bytes| {
728            right_capacity
729                .checked_mul(std::mem::size_of::<u32>() as u64)
730                .and_then(|next| bytes.checked_add(next))
731        })
732        .and_then(|bytes| bytes.checked_add(std::mem::size_of::<u64>() as u64))
733        .ok_or_else(|| XlogError::Kernel("resident join workspace byte overflow".into()))
734}
735
736/// Exact manager-tracked bytes for convergence status and counters.
737pub const fn resident_control_device_bytes() -> u64 {
738    (std::mem::size_of::<ResidentTerminalStatus>() + 2 * std::mem::size_of::<u32>()) as u64
739}
740
741/// Exact manager-tracked bytes for physical and legacy-semantic invocation counters.
742pub const fn resident_device_trace_bytes() -> u64 {
743    (4 * std::mem::size_of::<u32>()) as u64
744}
745
746/// Exact manager-tracked bytes for device first-nonempty schema selection.
747pub fn resident_schema_winners_device_bytes(head_count: usize) -> Result<u64> {
748    u64::try_from(head_count.max(1))
749        .ok()
750        .and_then(|count| count.checked_mul(2 * std::mem::size_of::<u32>() as u64))
751        .ok_or_else(|| XlogError::Kernel("resident schema winner byte overflow".into()))
752}
753
754fn resident_packed_receipt_device_bytes_for_fields(
755    head_count: usize,
756    schema_winner_count: usize,
757) -> Result<u64> {
758    let count_fields = head_count
759        .checked_add(4)
760        .and_then(|fields| fields.checked_add(schema_winner_count))
761        .ok_or_else(|| XlogError::Kernel("resident receipt count overflow".into()))?;
762    let pointer_bytes = u64::try_from(count_fields.max(1))
763        .ok()
764        .and_then(|count| count.checked_mul(std::mem::size_of::<u64>() as u64))
765        .ok_or_else(|| XlogError::Kernel("resident receipt pointer byte overflow".into()))?;
766    let packed_bytes = std::mem::size_of::<ResidentTerminalStatus>()
767        .checked_add(
768            count_fields
769                .checked_add(1)
770                .and_then(|fields| fields.checked_mul(std::mem::size_of::<u32>()))
771                .ok_or_else(|| XlogError::Kernel("resident receipt byte overflow".into()))?,
772        )
773        .ok_or_else(|| XlogError::Kernel("resident receipt byte overflow".into()))?;
774    pointer_bytes
775        .checked_add(packed_bytes as u64)
776        .ok_or_else(|| XlogError::Kernel("resident receipt total byte overflow".into()))
777}
778
779/// Exact manager-tracked bytes for the packed device receipt and pointer table.
780pub fn resident_packed_receipt_device_bytes(head_count: usize) -> Result<u64> {
781    resident_packed_receipt_device_bytes_for_fields(head_count, 0)
782}
783
784/// Exact device bytes when the receipt also carries one schema winner per head.
785pub fn resident_packed_receipt_with_schema_winners_device_bytes(head_count: usize) -> Result<u64> {
786    resident_packed_receipt_device_bytes_for_fields(head_count, head_count)
787}
788
789fn width(ty: ScalarType) -> Result<u32> {
790    match ty {
791        ScalarType::Symbol | ScalarType::U32 => Ok(4),
792        ScalarType::U64 => Ok(8),
793        other => Err(XlogError::Kernel(format!(
794            "resident relational type {other:?} is unsupported; expected Symbol, U32, or U64"
795        ))),
796    }
797}
798
799fn validate_schema(schema: &Schema) -> Result<()> {
800    if schema.arity() > RESIDENT_RELATIONAL_MAX_ARITY {
801        return Err(XlogError::Kernel(format!(
802            "resident relational arity {} exceeds {RESIDENT_RELATIONAL_MAX_ARITY}",
803            schema.arity()
804        )));
805    }
806    for column in 0..schema.arity() {
807        width(schema.column_type(column).expect("schema arity checked"))?;
808    }
809    Ok(())
810}
811
812fn relation_view(buffer: &CudaBuffer) -> Result<ResidentRelationView> {
813    validate_schema(buffer.schema())?;
814    let capacity = checked_capacity(buffer.num_rows(), "relation")?;
815    let mut columns = [0; RESIDENT_RELATIONAL_MAX_ARITY];
816    let mut widths = [0; RESIDENT_RELATIONAL_MAX_ARITY];
817    for column in 0..buffer.arity() {
818        columns[column] = *buffer.column(column).expect("arity checked").device_ptr();
819        widths[column] = width(
820            buffer
821                .schema()
822                .column_type(column)
823                .expect("schema arity checked"),
824        )?;
825    }
826    Ok(ResidentRelationView {
827        columns,
828        widths,
829        arity: buffer.arity() as u32,
830        capacity,
831        reserved: 0,
832        num_rows: buffer.num_rows_device().device_ptr_value(),
833    })
834}
835
836fn ensure_same_schema(a: &CudaBuffer, b: &CudaBuffer) -> Result<()> {
837    if !same_physical_layout(a.schema(), b.schema()) {
838        return Err(XlogError::Kernel(
839            "resident set operands must have identical physical layouts".to_string(),
840        ));
841    }
842    Ok(())
843}
844
845fn same_physical_layout(left: &Schema, right: &Schema) -> bool {
846    left.arity() == right.arity()
847        && (0..left.arity()).all(|column| left.column_type(column) == right.column_type(column))
848}
849
850fn launch_config(capacity: u32) -> LaunchConfig {
851    LaunchConfig {
852        grid_dim: (capacity.max(1).div_ceil(BLOCK_SIZE), 1, 1),
853        block_dim: (BLOCK_SIZE, 1, 1),
854        shared_mem_bytes: 0,
855    }
856}
857
858fn memset_u8_async(
859    ptr: sys::CUdeviceptr,
860    value: u8,
861    bytes: usize,
862    stream: &CudaStream,
863    label: &str,
864) -> Result<()> {
865    if bytes == 0 {
866        return Ok(());
867    }
868    // SAFETY: all callers pass live owned allocations and their exact byte size.
869    let code = unsafe { sys::cuMemsetD8Async(ptr, value, bytes, stream.cu_stream()) };
870    if code != sys::cudaError_enum::CUDA_SUCCESS {
871        return Err(XlogError::Kernel(format!(
872            "resident {label} memset failed: {code:?}"
873        )));
874    }
875    Ok(())
876}
877
878impl CudaKernelProvider {
879    /// Allocate a fixed-capacity resident output relation on the cold path.
880    pub fn prepare_resident_relation(
881        &self,
882        schema: Schema,
883        capacity: u64,
884    ) -> Result<ResidentRelation> {
885        self.prepare_resident_relation_with_reservation(schema, capacity, None)
886    }
887
888    /// Allocate a resident relation exclusively from an admitted transaction.
889    pub fn prepare_resident_relation_in_reservation(
890        &self,
891        schema: Schema,
892        capacity: u64,
893        reservation: &mut GpuMemoryReservation,
894    ) -> Result<ResidentRelation> {
895        self.prepare_resident_relation_with_reservation(schema, capacity, Some(reservation))
896    }
897
898    fn prepare_resident_relation_with_reservation(
899        &self,
900        schema: Schema,
901        capacity: u64,
902        mut reservation: Option<&mut GpuMemoryReservation>,
903    ) -> Result<ResidentRelation> {
904        if schema.arity() > RESIDENT_RELATIONAL_MAX_ARITY {
905            return Err(XlogError::Kernel(format!(
906                "resident relation arity {} exceeds {RESIDENT_RELATIONAL_MAX_ARITY}",
907                schema.arity()
908            )));
909        }
910        for column in 0..schema.arity() {
911            width(schema.column_type(column).expect("schema arity checked"))?;
912        }
913        let capacity_u32 = checked_capacity(capacity, "output")?;
914        let mut columns = Vec::with_capacity(schema.arity());
915        for column in 0..schema.arity() {
916            let bytes = (capacity as usize)
917                .checked_mul(width(schema.column_type(column).expect("schema"))? as usize)
918                .ok_or_else(|| XlogError::Kernel("resident column byte overflow".to_string()))?;
919            let column = match reservation.as_deref_mut() {
920                Some(reservation) => reservation.alloc::<u8>(bytes)?,
921                None => self.memory().alloc::<u8>(bytes)?,
922            };
923            columns.push(CudaColumn::Owned(column));
924        }
925        let d_num_rows = match reservation.as_deref_mut() {
926            Some(reservation) => reservation.alloc::<u32>(1)?,
927            None => self.memory().alloc::<u32>(1)?,
928        };
929        let buffer = CudaBuffer::from_columns(columns, capacity, d_num_rows, schema);
930        debug_assert_eq!(capacity_u32 as u64, capacity);
931        Ok(ResidentRelation { buffer })
932    }
933
934    /// Initialize a private resident relation's logical set cardinality.
935    ///
936    /// This cold-path write addresses the device scalar directly so it does
937    /// not populate or invalidate the buffer's host row-count cache.
938    pub fn initialize_resident_relation_count(
939        &self,
940        relation: &mut ResidentRelation,
941        initial_count: u32,
942    ) -> Result<()> {
943        if initial_count > 1 {
944            return Err(XlogError::Kernel(format!(
945                "resident relation initial count {initial_count} is invalid; expected 0 or 1"
946            )));
947        }
948        let code = unsafe {
949            sys::cuMemsetD32_v2(
950                relation.num_rows_device().device_ptr_value(),
951                initial_count,
952                1,
953            )
954        };
955        if code != sys::cudaError_enum::CUDA_SUCCESS {
956            return Err(XlogError::Kernel(format!(
957                "resident relation count initialization failed: {code:?}"
958            )));
959        }
960        Ok(())
961    }
962
963    /// Enqueue a graph-capturable logical-count clear on the supplied stream.
964    pub fn record_resident_relation_clear_on_stream(
965        &self,
966        relation: &ResidentRelation,
967        stream: &CudaStream,
968    ) -> Result<()> {
969        memset_u8_async(
970            relation.num_rows_device().device_ptr_value(),
971            0,
972            std::mem::size_of::<u32>(),
973            stream,
974            "relation count clear",
975        )
976    }
977
978    pub fn prepare_resident_set_workspace(
979        &self,
980        candidate_capacity: u64,
981    ) -> Result<ResidentSetWorkspace> {
982        self.prepare_resident_set_workspace_with_reservation(candidate_capacity, None)
983    }
984
985    /// Allocate set scratch exclusively from an admitted transaction.
986    pub fn prepare_resident_set_workspace_in_reservation(
987        &self,
988        candidate_capacity: u64,
989        reservation: &mut GpuMemoryReservation,
990    ) -> Result<ResidentSetWorkspace> {
991        self.prepare_resident_set_workspace_with_reservation(candidate_capacity, Some(reservation))
992    }
993
994    fn prepare_resident_set_workspace_with_reservation(
995        &self,
996        candidate_capacity: u64,
997        mut reservation: Option<&mut GpuMemoryReservation>,
998    ) -> Result<ResidentSetWorkspace> {
999        let candidate_capacity_u32 = checked_capacity(candidate_capacity, "set candidate")?;
1000        let slots = checked_hash_slot_capacity(candidate_capacity)?;
1001        let slot_storage = match reservation.as_deref_mut() {
1002            Some(reservation) => reservation.alloc::<u64>(slots as usize)?,
1003            None => self.memory().alloc::<u64>(slots as usize)?,
1004        };
1005        let required = match reservation.as_deref_mut() {
1006            Some(reservation) => reservation.alloc::<u64>(1)?,
1007            None => self.memory().alloc::<u64>(1)?,
1008        };
1009        Ok(ResidentSetWorkspace {
1010            slots: slot_storage,
1011            required,
1012            candidate_capacity: candidate_capacity_u32,
1013            slot_mask: (slots - 1) as u32,
1014        })
1015    }
1016
1017    pub fn prepare_resident_join_workspace(
1018        &self,
1019        right_capacity: u64,
1020    ) -> Result<ResidentJoinWorkspace> {
1021        self.prepare_resident_join_workspace_with_reservation(right_capacity, None)
1022    }
1023
1024    /// Allocate join scratch exclusively from an admitted transaction.
1025    pub fn prepare_resident_join_workspace_in_reservation(
1026        &self,
1027        right_capacity: u64,
1028        reservation: &mut GpuMemoryReservation,
1029    ) -> Result<ResidentJoinWorkspace> {
1030        self.prepare_resident_join_workspace_with_reservation(right_capacity, Some(reservation))
1031    }
1032
1033    fn prepare_resident_join_workspace_with_reservation(
1034        &self,
1035        right_capacity: u64,
1036        mut reservation: Option<&mut GpuMemoryReservation>,
1037    ) -> Result<ResidentJoinWorkspace> {
1038        let right_capacity_u32 = checked_capacity(right_capacity, "join right")?;
1039        let buckets = checked_hash_slot_capacity(right_capacity)?;
1040        let bucket_heads = match reservation.as_deref_mut() {
1041            Some(reservation) => reservation.alloc::<u32>(buckets as usize)?,
1042            None => self.memory().alloc::<u32>(buckets as usize)?,
1043        };
1044        let next = match reservation.as_deref_mut() {
1045            Some(reservation) => reservation.alloc::<u32>(right_capacity as usize)?,
1046            None => self.memory().alloc::<u32>(right_capacity as usize)?,
1047        };
1048        let required = match reservation.as_deref_mut() {
1049            Some(reservation) => reservation.alloc::<u64>(1)?,
1050            None => self.memory().alloc::<u64>(1)?,
1051        };
1052        Ok(ResidentJoinWorkspace {
1053            bucket_heads,
1054            next,
1055            required,
1056            right_capacity: right_capacity_u32,
1057            bucket_mask: (buckets - 1) as u32,
1058        })
1059    }
1060
1061    pub fn prepare_resident_convergence_control(&self) -> Result<ResidentConvergenceControl> {
1062        self.prepare_resident_convergence_control_with_reservation(None)
1063    }
1064
1065    /// Allocate convergence state exclusively from an admitted transaction.
1066    pub fn prepare_resident_convergence_control_in_reservation(
1067        &self,
1068        reservation: &mut GpuMemoryReservation,
1069    ) -> Result<ResidentConvergenceControl> {
1070        self.prepare_resident_convergence_control_with_reservation(Some(reservation))
1071    }
1072
1073    fn prepare_resident_convergence_control_with_reservation(
1074        &self,
1075        mut reservation: Option<&mut GpuMemoryReservation>,
1076    ) -> Result<ResidentConvergenceControl> {
1077        let status = match reservation.as_deref_mut() {
1078            Some(reservation) => reservation.alloc::<ResidentTerminalStatus>(1)?,
1079            None => self.memory().alloc::<ResidentTerminalStatus>(1)?,
1080        };
1081        let changed = match reservation.as_deref_mut() {
1082            Some(reservation) => reservation.alloc::<u32>(1)?,
1083            None => self.memory().alloc::<u32>(1)?,
1084        };
1085        let loop_iterations = match reservation.as_deref_mut() {
1086            Some(reservation) => reservation.alloc::<u32>(1)?,
1087            None => self.memory().alloc::<u32>(1)?,
1088        };
1089        Ok(ResidentConvergenceControl {
1090            status,
1091            changed,
1092            loop_iterations,
1093        })
1094    }
1095
1096    /// Allocate fixed-address device counters on the cold path.
1097    pub fn prepare_resident_device_trace(&self) -> Result<ResidentDeviceTrace> {
1098        self.prepare_resident_device_trace_with_reservation(None)
1099    }
1100
1101    /// Allocate device trace counters exclusively from an admitted transaction.
1102    pub fn prepare_resident_device_trace_in_reservation(
1103        &self,
1104        reservation: &mut GpuMemoryReservation,
1105    ) -> Result<ResidentDeviceTrace> {
1106        self.prepare_resident_device_trace_with_reservation(Some(reservation))
1107    }
1108
1109    fn prepare_resident_device_trace_with_reservation(
1110        &self,
1111        mut reservation: Option<&mut GpuMemoryReservation>,
1112    ) -> Result<ResidentDeviceTrace> {
1113        let scan_invocations = match reservation.as_deref_mut() {
1114            Some(reservation) => reservation.alloc::<u32>(1)?,
1115            None => self.memory().alloc::<u32>(1)?,
1116        };
1117        let filter_invocations = match reservation.as_deref_mut() {
1118            Some(reservation) => reservation.alloc::<u32>(1)?,
1119            None => self.memory().alloc::<u32>(1)?,
1120        };
1121        let semantic_scan_invocations = match reservation.as_deref_mut() {
1122            Some(reservation) => reservation.alloc::<u32>(1)?,
1123            None => self.memory().alloc::<u32>(1)?,
1124        };
1125        let semantic_filter_invocations = match reservation.as_deref_mut() {
1126            Some(reservation) => reservation.alloc::<u32>(1)?,
1127            None => self.memory().alloc::<u32>(1)?,
1128        };
1129        Ok(ResidentDeviceTrace {
1130            scan_invocations,
1131            filter_invocations,
1132            semantic_scan_invocations,
1133            semantic_filter_invocations,
1134        })
1135    }
1136
1137    /// Allocate device first-nonempty selectors and upload their cold defaults.
1138    pub fn prepare_resident_schema_winners(
1139        &self,
1140        default_schema_ids: &[u32],
1141    ) -> Result<ResidentSchemaWinners> {
1142        self.prepare_resident_schema_winners_with_reservation(default_schema_ids, None)
1143    }
1144
1145    /// Allocate schema selectors exclusively from an admitted transaction.
1146    pub fn prepare_resident_schema_winners_in_reservation(
1147        &self,
1148        default_schema_ids: &[u32],
1149        reservation: &mut GpuMemoryReservation,
1150    ) -> Result<ResidentSchemaWinners> {
1151        self.prepare_resident_schema_winners_with_reservation(default_schema_ids, Some(reservation))
1152    }
1153
1154    fn prepare_resident_schema_winners_with_reservation(
1155        &self,
1156        default_schema_ids: &[u32],
1157        mut reservation: Option<&mut GpuMemoryReservation>,
1158    ) -> Result<ResidentSchemaWinners> {
1159        let len = u32::try_from(default_schema_ids.len())
1160            .map_err(|_| XlogError::Kernel("too many resident schema winners".into()))?;
1161        let allocation_len = default_schema_ids.len().max(1);
1162        let seen_nonempty = match reservation.as_deref_mut() {
1163            Some(reservation) => reservation.alloc::<u32>(allocation_len)?,
1164            None => self.memory().alloc::<u32>(allocation_len)?,
1165        };
1166        let mut winner_schema_ids = match reservation.as_deref_mut() {
1167            Some(reservation) => reservation.alloc::<u32>(allocation_len)?,
1168            None => self.memory().alloc::<u32>(allocation_len)?,
1169        };
1170        if !default_schema_ids.is_empty() {
1171            self.device()
1172                .inner()
1173                .htod_sync_copy_into(default_schema_ids, &mut winner_schema_ids)
1174                .map_err(|error| {
1175                    XlogError::Kernel(format!("resident schema winner upload failed: {error}"))
1176                })?;
1177        }
1178        Ok(ResidentSchemaWinners {
1179            seen_nonempty,
1180            winner_schema_ids,
1181            default_schema_ids: default_schema_ids.to_vec(),
1182            len,
1183        })
1184    }
1185
1186    fn resident_receipt_pointee<T: cudarc::driver::DeviceRepr>(
1187        &self,
1188        role: ResidentReceiptPointeeRole,
1189        slice: &TrackedCudaSlice<T>,
1190        byte_offset: usize,
1191    ) -> Result<ResidentReceiptPointee> {
1192        let manager_id = slice.memory_manager_ptr_value();
1193        if manager_id != Arc::as_ptr(self.memory()) as usize {
1194            return Err(XlogError::Kernel(
1195                "resident receipt pointee belongs to a foreign memory manager".into(),
1196            ));
1197        }
1198        let provider_context = self.device().inner().stream().context();
1199        let slice_context = DeviceSlice::stream(slice).context();
1200        if !Arc::ptr_eq(slice_context, provider_context)
1201            || slice_context.cu_ctx() != provider_context.cu_ctx()
1202        {
1203            return Err(XlogError::Kernel(
1204                "resident receipt pointee belongs to a foreign CUDA context".into(),
1205            ));
1206        }
1207        let slice_bytes = slice
1208            .len()
1209            .checked_mul(std::mem::size_of::<T>())
1210            .ok_or_else(|| XlogError::Kernel("resident receipt slice byte size overflow".into()))?;
1211        let field_end = byte_offset
1212            .checked_add(4)
1213            .ok_or_else(|| XlogError::Kernel("resident receipt pointee offset overflow".into()))?;
1214        if field_end > slice_bytes {
1215            return Err(XlogError::Kernel(
1216                "resident receipt pointee is outside its source slice".into(),
1217            ));
1218        }
1219        let byte_offset = u64::try_from(byte_offset)
1220            .map_err(|_| XlogError::Kernel("resident receipt pointee offset overflow".into()))?;
1221        let ptr = slice
1222            .device_ptr_value()
1223            .checked_add(byte_offset)
1224            .ok_or_else(|| XlogError::Kernel("resident receipt pointee address overflow".into()))?;
1225        let runtime_block = match slice.runtime_block() {
1226            Some(block) => {
1227                if block.state != crate::device_runtime::BlockState::Live {
1228                    return Err(XlogError::Kernel(
1229                        "resident receipt pointee runtime block is not live".into(),
1230                    ));
1231                }
1232                Some((
1233                    crate::device_runtime::BlockId::from_block(block),
1234                    block.bytes,
1235                ))
1236            }
1237            None => None,
1238        };
1239        checked_receipt_pointee(role, ptr, manager_id, runtime_block)
1240    }
1241
1242    /// Allocate and upload the immutable relation-count pointer table on the cold path.
1243    pub fn prepare_resident_packed_receipt(
1244        &self,
1245        relations: &[&ResidentRelation],
1246    ) -> Result<ResidentPackedReceipt> {
1247        let relation_count_len = u32::try_from(relations.len())
1248            .map_err(|_| XlogError::Kernel("resident receipt has too many count fields".into()))?;
1249        let pointees = relations
1250            .iter()
1251            .enumerate()
1252            .map(|(index, relation)| {
1253                let index = u32::try_from(index).map_err(|_| {
1254                    XlogError::Kernel("resident receipt head index overflow".into())
1255                })?;
1256                self.resident_receipt_pointee(
1257                    ResidentReceiptPointeeRole::RelationCount(index),
1258                    relation.num_rows_device(),
1259                    0,
1260                )
1261            })
1262            .collect::<Result<Vec<_>>>()?;
1263        self.prepare_resident_packed_receipt_from_pointees(pointees, relation_count_len, 0, 0, None)
1264    }
1265
1266    /// Extend the final receipt with physical then legacy-semantic scan/filter counters.
1267    pub fn prepare_resident_packed_receipt_with_trace(
1268        &self,
1269        relations: &[&ResidentRelation],
1270        trace: &ResidentDeviceTrace,
1271    ) -> Result<ResidentPackedReceipt> {
1272        self.prepare_resident_packed_receipt_with_trace_and_reservation(relations, trace, None)
1273    }
1274
1275    /// Allocate the device receipt exclusively from an admitted transaction.
1276    pub fn prepare_resident_packed_receipt_with_trace_in_reservation(
1277        &self,
1278        relations: &[&ResidentRelation],
1279        trace: &ResidentDeviceTrace,
1280        reservation: &mut GpuMemoryReservation,
1281    ) -> Result<ResidentPackedReceipt> {
1282        self.prepare_resident_packed_receipt_with_trace_and_reservation(
1283            relations,
1284            trace,
1285            Some(reservation),
1286        )
1287    }
1288
1289    fn prepare_resident_packed_receipt_with_trace_and_reservation(
1290        &self,
1291        relations: &[&ResidentRelation],
1292        trace: &ResidentDeviceTrace,
1293        reservation: Option<&mut GpuMemoryReservation>,
1294    ) -> Result<ResidentPackedReceipt> {
1295        let relation_count_len = u32::try_from(relations.len())
1296            .map_err(|_| XlogError::Kernel("resident receipt has too many count fields".into()))?;
1297        let mut pointees = Vec::with_capacity(relations.len().saturating_add(4));
1298        for (index, relation) in relations.iter().enumerate() {
1299            let index = u32::try_from(index)
1300                .map_err(|_| XlogError::Kernel("resident receipt head index overflow".into()))?;
1301            pointees.push(self.resident_receipt_pointee(
1302                ResidentReceiptPointeeRole::RelationCount(index),
1303                relation.num_rows_device(),
1304                0,
1305            )?);
1306        }
1307        pointees.push(self.resident_receipt_pointee(
1308            ResidentReceiptPointeeRole::ScanTrace,
1309            &trace.scan_invocations,
1310            0,
1311        )?);
1312        pointees.push(self.resident_receipt_pointee(
1313            ResidentReceiptPointeeRole::FilterTrace,
1314            &trace.filter_invocations,
1315            0,
1316        )?);
1317        pointees.push(self.resident_receipt_pointee(
1318            ResidentReceiptPointeeRole::SemanticScanTrace,
1319            &trace.semantic_scan_invocations,
1320            0,
1321        )?);
1322        pointees.push(self.resident_receipt_pointee(
1323            ResidentReceiptPointeeRole::SemanticFilterTrace,
1324            &trace.semantic_filter_invocations,
1325            0,
1326        )?);
1327        self.prepare_resident_packed_receipt_from_pointees(
1328            pointees,
1329            relation_count_len,
1330            4,
1331            0,
1332            reservation,
1333        )
1334    }
1335
1336    /// Extend the final receipt with trace counters and staged-head schema winners.
1337    pub fn prepare_resident_packed_receipt_with_trace_and_schema_winners(
1338        &self,
1339        relations: &[&ResidentRelation],
1340        trace: &ResidentDeviceTrace,
1341        winners: &ResidentSchemaWinners,
1342    ) -> Result<ResidentPackedReceipt> {
1343        self.prepare_resident_packed_receipt_with_trace_and_schema_winners_and_reservation(
1344            relations, trace, winners, None,
1345        )
1346    }
1347
1348    /// Allocate the schema-winning receipt exclusively from an admitted transaction.
1349    pub fn prepare_resident_packed_receipt_with_trace_and_schema_winners_in_reservation(
1350        &self,
1351        relations: &[&ResidentRelation],
1352        trace: &ResidentDeviceTrace,
1353        winners: &ResidentSchemaWinners,
1354        reservation: &mut GpuMemoryReservation,
1355    ) -> Result<ResidentPackedReceipt> {
1356        self.prepare_resident_packed_receipt_with_trace_and_schema_winners_and_reservation(
1357            relations,
1358            trace,
1359            winners,
1360            Some(reservation),
1361        )
1362    }
1363
1364    fn prepare_resident_packed_receipt_with_trace_and_schema_winners_and_reservation(
1365        &self,
1366        relations: &[&ResidentRelation],
1367        trace: &ResidentDeviceTrace,
1368        winners: &ResidentSchemaWinners,
1369        reservation: Option<&mut GpuMemoryReservation>,
1370    ) -> Result<ResidentPackedReceipt> {
1371        if winners.len() as usize != relations.len() {
1372            return Err(XlogError::Kernel(format!(
1373                "resident schema winner count {} does not match relation count {}",
1374                winners.len(),
1375                relations.len()
1376            )));
1377        }
1378        let relation_count_len = u32::try_from(relations.len())
1379            .map_err(|_| XlogError::Kernel("resident receipt has too many count fields".into()))?;
1380        let mut pointees = Vec::with_capacity(relations.len().saturating_mul(2).saturating_add(4));
1381        for (index, relation) in relations.iter().enumerate() {
1382            let index = u32::try_from(index)
1383                .map_err(|_| XlogError::Kernel("resident receipt head index overflow".into()))?;
1384            pointees.push(self.resident_receipt_pointee(
1385                ResidentReceiptPointeeRole::RelationCount(index),
1386                relation.num_rows_device(),
1387                0,
1388            )?);
1389        }
1390        pointees.push(self.resident_receipt_pointee(
1391            ResidentReceiptPointeeRole::ScanTrace,
1392            &trace.scan_invocations,
1393            0,
1394        )?);
1395        pointees.push(self.resident_receipt_pointee(
1396            ResidentReceiptPointeeRole::FilterTrace,
1397            &trace.filter_invocations,
1398            0,
1399        )?);
1400        pointees.push(self.resident_receipt_pointee(
1401            ResidentReceiptPointeeRole::SemanticScanTrace,
1402            &trace.semantic_scan_invocations,
1403            0,
1404        )?);
1405        pointees.push(self.resident_receipt_pointee(
1406            ResidentReceiptPointeeRole::SemanticFilterTrace,
1407            &trace.semantic_filter_invocations,
1408            0,
1409        )?);
1410        for index in 0..relations.len() {
1411            let index_u32 = u32::try_from(index)
1412                .map_err(|_| XlogError::Kernel("resident receipt head index overflow".into()))?;
1413            let byte_offset = index
1414                .checked_mul(std::mem::size_of::<u32>())
1415                .ok_or_else(|| {
1416                    XlogError::Kernel("resident receipt winner offset overflow".into())
1417                })?;
1418            pointees.push(self.resident_receipt_pointee(
1419                ResidentReceiptPointeeRole::SchemaWinner(index_u32),
1420                &winners.winner_schema_ids,
1421                byte_offset,
1422            )?);
1423        }
1424        self.prepare_resident_packed_receipt_from_pointees(
1425            pointees,
1426            relation_count_len,
1427            4,
1428            winners.len(),
1429            reservation,
1430        )
1431    }
1432
1433    fn prepare_resident_packed_receipt_from_pointees(
1434        &self,
1435        pointees: Vec<ResidentReceiptPointee>,
1436        relation_count_len: u32,
1437        device_trace_field_count: u32,
1438        schema_winner_count: u32,
1439        mut reservation: Option<&mut GpuMemoryReservation>,
1440    ) -> Result<ResidentPackedReceipt> {
1441        validate_receipt_pointee_ranges(&pointees)?;
1442        let expected_count_len = relation_count_len
1443            .checked_add(device_trace_field_count)
1444            .and_then(|count| count.checked_add(schema_winner_count))
1445            .ok_or_else(|| XlogError::Kernel("resident receipt field count overflow".into()))?;
1446        if usize::try_from(expected_count_len).ok() != Some(pointees.len()) {
1447            return Err(XlogError::Kernel(
1448                "resident receipt pointee manifest count does not match its shape".into(),
1449            ));
1450        }
1451        let count_ptrs: Vec<u64> = pointees.iter().map(|pointee| pointee.ptr).collect();
1452        let count_len = u32::try_from(count_ptrs.len())
1453            .map_err(|_| XlogError::Kernel("resident receipt has too many count fields".into()))?;
1454        let mut d_count_ptrs = match reservation.as_deref_mut() {
1455            Some(reservation) => reservation.alloc::<u64>(count_ptrs.len().max(1))?,
1456            None => self.memory().alloc::<u64>(count_ptrs.len().max(1))?,
1457        };
1458        if !count_ptrs.is_empty() {
1459            self.device()
1460                .inner()
1461                .htod_sync_copy_into(&count_ptrs, &mut d_count_ptrs)
1462                .map_err(|error| {
1463                    XlogError::Kernel(format!("resident receipt pointer upload failed: {error}"))
1464                })?;
1465        }
1466        let u32_fields = count_ptrs
1467            .len()
1468            .checked_add(1)
1469            .ok_or_else(|| XlogError::Kernel("resident receipt field count overflow".into()))?;
1470        let bytes =
1471            std::mem::size_of::<ResidentTerminalStatus>()
1472                .checked_add(u32_fields.checked_mul(4).ok_or_else(|| {
1473                    XlogError::Kernel("resident receipt byte size overflow".into())
1474                })?)
1475                .ok_or_else(|| XlogError::Kernel("resident receipt byte size overflow".into()))?;
1476        let bytes = match reservation.as_deref_mut() {
1477            Some(reservation) => reservation.alloc::<u8>(bytes)?,
1478            None => self.memory().alloc::<u8>(bytes)?,
1479        };
1480        Ok(ResidentPackedReceipt {
1481            count_ptrs: d_count_ptrs,
1482            bytes,
1483            pointees,
1484            count_len,
1485            relation_count_len,
1486            device_trace_field_count,
1487            schema_winner_count,
1488        })
1489    }
1490
1491    /// Allocate the final receipt's exact byte length in page-locked memory.
1492    pub fn prepare_resident_pinned_receipt(
1493        &self,
1494        receipt: &ResidentPackedReceipt,
1495    ) -> Result<ResidentPinnedReceipt> {
1496        let mut ptr = std::ptr::null_mut();
1497        // SAFETY: CUDA initializes `ptr` on success; the owner frees it once.
1498        let code = unsafe { sys::cuMemHostAlloc(&mut ptr, receipt.len_bytes(), 0) };
1499        if code != sys::cudaError_enum::CUDA_SUCCESS {
1500            return Err(XlogError::Kernel(format!(
1501                "resident pinned receipt allocation failed: {code:?}"
1502            )));
1503        }
1504        let ptr = std::ptr::NonNull::new(ptr.cast()).ok_or_else(|| {
1505            XlogError::Kernel("resident pinned receipt allocation returned null".into())
1506        })?;
1507        Ok(ResidentPinnedReceipt {
1508            ptr,
1509            len: receipt.len_bytes(),
1510        })
1511    }
1512
1513    /// Copy the packed receipt once after core execution has synchronized.
1514    ///
1515    /// The destination was allocated on the cold path. This copy is accounted
1516    /// only in the final-observation counters after its stream wait succeeds.
1517    pub fn observe_resident_packed_receipt(
1518        &self,
1519        receipt: &ResidentPackedReceipt,
1520        pinned: &mut ResidentPinnedReceipt,
1521        stream: &CudaStream,
1522    ) -> Result<Vec<u8>> {
1523        if pinned.len != receipt.len_bytes() {
1524            return Err(XlogError::Kernel(format!(
1525                "resident pinned receipt size {} does not match device receipt size {}",
1526                pinned.len,
1527                receipt.len_bytes()
1528            )));
1529        }
1530        // SAFETY: both allocations are live for `pinned.len` bytes and the
1531        // mutable owner prevents host access until this stream is synchronized.
1532        let code = unsafe {
1533            sys::cuMemcpyDtoHAsync_v2(
1534                pinned.ptr.as_ptr().cast(),
1535                receipt.device_bytes().device_ptr_value(),
1536                pinned.len,
1537                stream.cu_stream(),
1538            )
1539        };
1540        if code != sys::cudaError_enum::CUDA_SUCCESS {
1541            return Err(XlogError::Kernel(format!(
1542                "resident final receipt copy failed: {code:?}"
1543            )));
1544        }
1545        stream.synchronize().map_err(|error| {
1546            XlogError::Kernel(format!("resident final receipt wait failed: {error}"))
1547        })?;
1548        // SAFETY: the copy and stream wait succeeded for the complete owner.
1549        let bytes = unsafe { std::slice::from_raw_parts(pinned.ptr.as_ptr(), pinned.len) }.to_vec();
1550        self.record_final_observation_transfer(pinned.len as u64);
1551        Ok(bytes)
1552    }
1553
1554    /// Enqueue one device-only terminal/count pack before the single final D2H.
1555    pub fn record_resident_receipt_pack_on_stream(
1556        &self,
1557        control: &ResidentConvergenceControl,
1558        receipt: &ResidentPackedReceipt,
1559        stream: &CudaStream,
1560    ) -> Result<()> {
1561        let function = self
1562            .device()
1563            .inner()
1564            .get_func(MODULE, "resident_receipt_pack")
1565            .ok_or_else(|| XlogError::Kernel("resident_receipt_pack kernel missing".into()))?;
1566        let status = control.status.device_ptr_value();
1567        let changed = control.changed.device_ptr_value();
1568        let count_ptrs = receipt.count_ptrs.device_ptr_value();
1569        let output = receipt.bytes.device_ptr_value();
1570        let mut params = vec![
1571            status.as_kernel_param(),
1572            changed.as_kernel_param(),
1573            count_ptrs.as_kernel_param(),
1574            receipt.count_len.as_kernel_param(),
1575            output.as_kernel_param(),
1576        ];
1577        // SAFETY: parameter list exactly matches resident_receipt_pack.
1578        unsafe {
1579            function
1580                .clone()
1581                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
1582        }
1583        .map_err(|error| XlogError::Kernel(format!("resident receipt pack launch: {error}")))
1584    }
1585
1586    /// Initialize staged-head seen flags from their post-seed device counts.
1587    pub fn record_resident_schema_winners_initialize_on_stream(
1588        &self,
1589        winners: &ResidentSchemaWinners,
1590        receipt: &ResidentPackedReceipt,
1591        stream: &CudaStream,
1592    ) -> Result<()> {
1593        if receipt.relation_count_len != winners.len {
1594            return Err(XlogError::Kernel(format!(
1595                "resident schema winner count {} does not match receipt relation count {}",
1596                winners.len, receipt.relation_count_len
1597            )));
1598        }
1599        if winners.len == 0 {
1600            return Ok(());
1601        }
1602        let function = self
1603            .device()
1604            .inner()
1605            .get_func(MODULE, "resident_schema_winners_initialize")
1606            .ok_or_else(|| {
1607                XlogError::Kernel("resident_schema_winners_initialize kernel missing".into())
1608            })?;
1609        let count_ptrs = receipt.count_ptrs.device_ptr_value();
1610        let seen_nonempty = winners.seen_nonempty.device_ptr_value();
1611        let mut params = vec![
1612            count_ptrs.as_kernel_param(),
1613            winners.len.as_kernel_param(),
1614            seen_nonempty.as_kernel_param(),
1615        ];
1616        // SAFETY: parameter list exactly matches resident_schema_winners_initialize.
1617        unsafe {
1618            function.clone().launch_on_stream(
1619                stream,
1620                LaunchConfig::for_num_elems(winners.len),
1621                &mut params,
1622            )
1623        }
1624        .map_err(|error| XlogError::Kernel(format!("resident schema winner init launch: {error}")))
1625    }
1626
1627    /// Select the first nonempty contribution's schema without host observation.
1628    pub fn record_resident_schema_winner_mark_on_stream(
1629        &self,
1630        contribution_count: &TrackedCudaSlice<u32>,
1631        winners: &ResidentSchemaWinners,
1632        head_index: u32,
1633        schema_id: u32,
1634        stream: &CudaStream,
1635    ) -> Result<()> {
1636        if head_index >= winners.len {
1637            return Err(XlogError::Kernel(format!(
1638                "resident schema winner index {head_index} exceeds {} heads",
1639                winners.len
1640            )));
1641        }
1642        let function = self
1643            .device()
1644            .inner()
1645            .get_func(MODULE, "resident_schema_winner_mark")
1646            .ok_or_else(|| {
1647                XlogError::Kernel("resident_schema_winner_mark kernel missing".into())
1648            })?;
1649        let contribution_count = contribution_count.device_ptr_value();
1650        let seen_nonempty = winners.seen_nonempty.device_ptr_value();
1651        let winner_schema_ids = winners.winner_schema_ids.device_ptr_value();
1652        let mut params = vec![
1653            contribution_count.as_kernel_param(),
1654            seen_nonempty.as_kernel_param(),
1655            winner_schema_ids.as_kernel_param(),
1656            head_index.as_kernel_param(),
1657            schema_id.as_kernel_param(),
1658        ];
1659        // SAFETY: parameter list exactly matches resident_schema_winner_mark.
1660        unsafe {
1661            function
1662                .clone()
1663                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
1664        }
1665        .map_err(|error| XlogError::Kernel(format!("resident schema winner mark launch: {error}")))
1666    }
1667
1668    /// Reset per-run trace counters inside the captured graph.
1669    pub fn record_resident_device_trace_initialize_on_stream(
1670        &self,
1671        trace: &ResidentDeviceTrace,
1672        stream: &CudaStream,
1673    ) -> Result<()> {
1674        let function = self
1675            .device()
1676            .inner()
1677            .get_func(MODULE, "resident_trace_initialize")
1678            .ok_or_else(|| XlogError::Kernel("resident_trace_initialize kernel missing".into()))?;
1679        let scan_invocations = trace.scan_invocations.device_ptr_value();
1680        let filter_invocations = trace.filter_invocations.device_ptr_value();
1681        let semantic_scan_invocations = trace.semantic_scan_invocations.device_ptr_value();
1682        let semantic_filter_invocations = trace.semantic_filter_invocations.device_ptr_value();
1683        let mut params = vec![
1684            scan_invocations.as_kernel_param(),
1685            filter_invocations.as_kernel_param(),
1686            semantic_scan_invocations.as_kernel_param(),
1687            semantic_filter_invocations.as_kernel_param(),
1688        ];
1689        // SAFETY: parameter list exactly matches resident_trace_initialize.
1690        unsafe {
1691            function
1692                .clone()
1693                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
1694        }
1695        .map_err(|error| XlogError::Kernel(format!("resident trace init launch: {error}")))
1696    }
1697
1698    /// Count one actual resident scan invocation on the device.
1699    pub fn record_resident_scan_trace_on_stream(
1700        &self,
1701        trace: &ResidentDeviceTrace,
1702        stream: &CudaStream,
1703    ) -> Result<()> {
1704        self.record_resident_trace_increment_on_stream(
1705            trace.scan_invocations.device_ptr_value(),
1706            trace.semantic_scan_invocations.device_ptr_value(),
1707            "scan",
1708            stream,
1709        )
1710    }
1711
1712    /// Count one actual resident filter invocation on the device.
1713    pub fn record_resident_filter_trace_on_stream(
1714        &self,
1715        trace: &ResidentDeviceTrace,
1716        stream: &CudaStream,
1717    ) -> Result<()> {
1718        self.record_resident_trace_increment_on_stream(
1719            trace.filter_invocations.device_ptr_value(),
1720            trace.semantic_filter_invocations.device_ptr_value(),
1721            "filter",
1722            stream,
1723        )
1724    }
1725
1726    fn record_resident_trace_increment_on_stream(
1727        &self,
1728        counter: u64,
1729        semantic_counter: u64,
1730        label: &str,
1731        stream: &CudaStream,
1732    ) -> Result<()> {
1733        let function = self
1734            .device()
1735            .inner()
1736            .get_func(MODULE, "resident_trace_increment")
1737            .ok_or_else(|| XlogError::Kernel("resident_trace_increment kernel missing".into()))?;
1738        let mut params = vec![
1739            counter.as_kernel_param(),
1740            semantic_counter.as_kernel_param(),
1741        ];
1742        // SAFETY: parameter list exactly matches resident_trace_increment.
1743        unsafe {
1744            function
1745                .clone()
1746                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
1747        }
1748        .map_err(|error| {
1749            XlogError::Kernel(format!("resident {label} trace increment launch: {error}"))
1750        })
1751    }
1752
1753    /// Enqueue initialization once before the conditional WHILE node executes.
1754    pub fn record_resident_control_initialize_on_stream(
1755        &self,
1756        control: &ResidentConvergenceControl,
1757        stream: &CudaStream,
1758    ) -> Result<()> {
1759        let function = self
1760            .device()
1761            .inner()
1762            .get_func(MODULE, "resident_control_initialize")
1763            .ok_or_else(|| {
1764                XlogError::Kernel("resident_control_initialize kernel missing".into())
1765            })?;
1766        let status = control.status.device_ptr_value();
1767        let changed = control.changed.device_ptr_value();
1768        let loop_iterations = control.loop_iterations.device_ptr_value();
1769        let mut params = vec![
1770            status.as_kernel_param(),
1771            changed.as_kernel_param(),
1772            loop_iterations.as_kernel_param(),
1773        ];
1774        // SAFETY: parameter list exactly matches the manifest kernel signature.
1775        unsafe {
1776            function
1777                .clone()
1778                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
1779        }
1780        .map_err(|error| XlogError::Kernel(format!("resident control init launch: {error}")))
1781    }
1782
1783    /// Reset per-SCC state without clearing a prior transaction error.
1784    /// A zero limit publishes IterationLimit with zero completed body replays.
1785    pub fn record_resident_scc_begin_on_stream(
1786        &self,
1787        iteration_limit: u32,
1788        op_id: u32,
1789        control: &ResidentConvergenceControl,
1790        stream: &CudaStream,
1791    ) -> Result<()> {
1792        let function = self
1793            .device()
1794            .inner()
1795            .get_func(MODULE, "resident_scc_begin")
1796            .ok_or_else(|| XlogError::Kernel("resident_scc_begin kernel missing".into()))?;
1797        let status = control.status.device_ptr_value();
1798        let changed = control.changed.device_ptr_value();
1799        let loop_iterations = control.loop_iterations.device_ptr_value();
1800        let mut params = vec![
1801            iteration_limit.as_kernel_param(),
1802            op_id.as_kernel_param(),
1803            status.as_kernel_param(),
1804            changed.as_kernel_param(),
1805            loop_iterations.as_kernel_param(),
1806        ];
1807        // SAFETY: parameter list exactly matches resident_scc_begin.
1808        unsafe {
1809            function
1810                .clone()
1811                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
1812        }
1813        .map_err(|error| XlogError::Kernel(format!("resident SCC begin launch: {error}")))
1814    }
1815
1816    pub fn record_resident_dedup_on_stream(
1817        &self,
1818        input: &CudaBuffer,
1819        output: &ResidentRelation,
1820        workspace: &ResidentSetWorkspace,
1821        control: &ResidentConvergenceControl,
1822        op_id: u32,
1823        stream: &CudaStream,
1824    ) -> Result<()> {
1825        ensure_same_schema(input, output.buffer())?;
1826        self.record_resident_set_on_stream(
1827            input, input, output, workspace, control, op_id, stream, 0,
1828        )
1829    }
1830
1831    pub fn record_resident_union_on_stream(
1832        &self,
1833        left: &CudaBuffer,
1834        right: &CudaBuffer,
1835        output: &ResidentRelation,
1836        workspace: &ResidentSetWorkspace,
1837        control: &ResidentConvergenceControl,
1838        op_id: u32,
1839        stream: &CudaStream,
1840    ) -> Result<()> {
1841        ensure_same_schema(left, right)?;
1842        ensure_same_schema(left, output.buffer())?;
1843        self.record_resident_set_on_stream(
1844            left, right, output, workspace, control, op_id, stream, 1,
1845        )
1846    }
1847
1848    pub fn record_resident_diff_on_stream(
1849        &self,
1850        left: &CudaBuffer,
1851        right: &CudaBuffer,
1852        output: &ResidentRelation,
1853        workspace: &ResidentSetWorkspace,
1854        control: &ResidentConvergenceControl,
1855        op_id: u32,
1856        stream: &CudaStream,
1857    ) -> Result<()> {
1858        ensure_same_schema(left, right)?;
1859        ensure_same_schema(left, output.buffer())?;
1860        self.record_resident_set_on_stream(
1861            left, right, output, workspace, control, op_id, stream, 2,
1862        )
1863    }
1864
1865    #[allow(clippy::too_many_arguments)]
1866    fn record_resident_set_on_stream(
1867        &self,
1868        left: &CudaBuffer,
1869        right: &CudaBuffer,
1870        output: &ResidentRelation,
1871        workspace: &ResidentSetWorkspace,
1872        control: &ResidentConvergenceControl,
1873        op_id: u32,
1874        stream: &CudaStream,
1875        mode: u32,
1876    ) -> Result<()> {
1877        let total = left
1878            .num_rows()
1879            .checked_add(if mode == 0 { 0 } else { right.num_rows() })
1880            .ok_or_else(|| XlogError::Kernel("resident set capacity overflow".into()))?;
1881        if total > u64::from(workspace.candidate_capacity) {
1882            return Err(XlogError::Kernel(format!(
1883                "resident set workspace capacity {} is below required candidate capacity {total}",
1884                workspace.candidate_capacity
1885            )));
1886        }
1887        memset_u8_async(
1888            workspace.slots.device_ptr_value(),
1889            0,
1890            workspace.slots.len() * 8,
1891            stream,
1892            "set slots",
1893        )?;
1894        memset_u8_async(
1895            workspace.required.device_ptr_value(),
1896            0,
1897            8,
1898            stream,
1899            "set required",
1900        )?;
1901        memset_u8_async(
1902            output.num_rows_device().device_ptr_value(),
1903            0,
1904            4,
1905            stream,
1906            "set output count",
1907        )?;
1908        let left_view = relation_view(left)?;
1909        let right_view = relation_view(right)?;
1910        let output_view = relation_view(output.buffer())?;
1911        let insert = self
1912            .device()
1913            .inner()
1914            .get_func(MODULE, "resident_set_insert")
1915            .ok_or_else(|| XlogError::Kernel("resident_set_insert kernel missing".into()))?;
1916        let slots = workspace.slots.device_ptr_value();
1917        let required = workspace.required.device_ptr_value();
1918        let status = control.status.device_ptr_value();
1919        let launch_insert = |candidate: ResidentRelationView,
1920                             source_tag: u32,
1921                             emit_rows: u32,
1922                             materialize: u32|
1923         -> Result<()> {
1924            let mut params = vec![
1925                candidate.as_kernel_param(),
1926                left_view.as_kernel_param(),
1927                right_view.as_kernel_param(),
1928                source_tag.as_kernel_param(),
1929                emit_rows.as_kernel_param(),
1930                materialize.as_kernel_param(),
1931                slots.as_kernel_param(),
1932                workspace.slot_mask.as_kernel_param(),
1933                output_view.as_kernel_param(),
1934                required.as_kernel_param(),
1935                status.as_kernel_param(),
1936                op_id.as_kernel_param(),
1937            ];
1938            // SAFETY: parameter list exactly matches resident_set_insert.
1939            unsafe {
1940                insert.clone().launch_on_stream(
1941                    stream,
1942                    launch_config(candidate.capacity),
1943                    &mut params,
1944                )
1945            }
1946            .map_err(|error| XlogError::Kernel(format!("resident set insert launch: {error}")))
1947        };
1948        let launch_pass = |materialize: u32| -> Result<()> {
1949            match mode {
1950                0 => launch_insert(left_view, 0, 1, materialize)?,
1951                1 => {
1952                    launch_insert(left_view, 0, 1, materialize)?;
1953                    launch_insert(right_view, 1, 1, materialize)?;
1954                }
1955                2 => {
1956                    launch_insert(right_view, 1, 0, materialize)?;
1957                    launch_insert(left_view, 0, 1, materialize)?;
1958                }
1959                _ => unreachable!(),
1960            }
1961            Ok(())
1962        };
1963        launch_pass(0)?;
1964        self.record_resident_finalize_on_stream(
1965            "resident_set_finalize",
1966            required,
1967            output_view,
1968            status,
1969            op_id,
1970            stream,
1971        )?;
1972        memset_u8_async(
1973            workspace.slots.device_ptr_value(),
1974            0,
1975            workspace.slots.len() * 8,
1976            stream,
1977            "set materialization slots",
1978        )?;
1979        memset_u8_async(
1980            workspace.required.device_ptr_value(),
1981            0,
1982            8,
1983            stream,
1984            "set materialization required",
1985        )?;
1986        launch_pass(1)
1987    }
1988
1989    #[allow(clippy::too_many_arguments)]
1990    pub fn record_resident_join_on_stream(
1991        &self,
1992        kind: ResidentJoinKind,
1993        left: &CudaBuffer,
1994        left_key: usize,
1995        right: &CudaBuffer,
1996        right_key: usize,
1997        output: &ResidentRelation,
1998        workspace: &ResidentJoinWorkspace,
1999        control: &ResidentConvergenceControl,
2000        op_id: u32,
2001        stream: &CudaStream,
2002    ) -> Result<()> {
2003        let left_view = relation_view(left)?;
2004        let right_view = relation_view(right)?;
2005        if right_view.capacity > workspace.right_capacity {
2006            return Err(XlogError::Kernel(format!(
2007                "resident join workspace right capacity {} is below relation capacity {}",
2008                workspace.right_capacity, right_view.capacity
2009            )));
2010        }
2011        if left_key >= left.arity() || right_key >= right.arity() {
2012            return Err(XlogError::Kernel(
2013                "resident join key index out of bounds".into(),
2014            ));
2015        }
2016        if width(left.schema().column_type(left_key).expect("key checked"))?
2017            != width(right.schema().column_type(right_key).expect("key checked"))?
2018        {
2019            return Err(XlogError::Kernel(
2020                "resident join key widths must match".into(),
2021            ));
2022        }
2023        let expected_schema = match kind {
2024            ResidentJoinKind::Semi => left.schema().clone(),
2025            ResidentJoinKind::Inner => {
2026                let mut columns = left.schema().columns.clone();
2027                columns.extend(right.schema().columns.iter().cloned());
2028                Schema::new(columns)
2029            }
2030        };
2031        if !same_physical_layout(output.buffer().schema(), &expected_schema) {
2032            return Err(XlogError::Kernel(
2033                "resident join output schema does not match join kind".into(),
2034            ));
2035        }
2036        let output_view = relation_view(output.buffer())?;
2037        memset_u8_async(
2038            workspace.bucket_heads.device_ptr_value(),
2039            0xff,
2040            workspace.bucket_heads.len() * 4,
2041            stream,
2042            "join buckets",
2043        )?;
2044        memset_u8_async(
2045            workspace.required.device_ptr_value(),
2046            0,
2047            8,
2048            stream,
2049            "join required",
2050        )?;
2051        memset_u8_async(
2052            output.num_rows_device().device_ptr_value(),
2053            0,
2054            4,
2055            stream,
2056            "join output count",
2057        )?;
2058        let buckets = workspace.bucket_heads.device_ptr_value();
2059        let next = workspace.next.device_ptr_value();
2060        let required = workspace.required.device_ptr_value();
2061        let status = control.status.device_ptr_value();
2062        let left_key = left_key as u32;
2063        let right_key = right_key as u32;
2064        let build = self
2065            .device()
2066            .inner()
2067            .get_func(MODULE, "resident_join_build")
2068            .ok_or_else(|| XlogError::Kernel("resident_join_build kernel missing".into()))?;
2069        let mut build_params = vec![
2070            right_view.as_kernel_param(),
2071            right_key.as_kernel_param(),
2072            buckets.as_kernel_param(),
2073            workspace.bucket_mask.as_kernel_param(),
2074            next.as_kernel_param(),
2075            status.as_kernel_param(),
2076            op_id.as_kernel_param(),
2077        ];
2078        // SAFETY: parameters exactly match resident_join_build.
2079        unsafe {
2080            build.clone().launch_on_stream(
2081                stream,
2082                launch_config(right_view.capacity),
2083                &mut build_params,
2084            )
2085        }
2086        .map_err(|error| XlogError::Kernel(format!("resident join build launch: {error}")))?;
2087        let probe_name = match kind {
2088            ResidentJoinKind::Inner => "resident_join_probe_inner",
2089            ResidentJoinKind::Semi => "resident_join_probe_semi",
2090        };
2091        let probe = self
2092            .device()
2093            .inner()
2094            .get_func(MODULE, probe_name)
2095            .ok_or_else(|| XlogError::Kernel(format!("{probe_name} kernel missing")))?;
2096        let launch_probe = |materialize: u32| -> Result<()> {
2097            let mut probe_params = vec![
2098                left_view.as_kernel_param(),
2099                left_key.as_kernel_param(),
2100                right_view.as_kernel_param(),
2101                right_key.as_kernel_param(),
2102                buckets.as_kernel_param(),
2103                workspace.bucket_mask.as_kernel_param(),
2104                next.as_kernel_param(),
2105                output_view.as_kernel_param(),
2106                required.as_kernel_param(),
2107                materialize.as_kernel_param(),
2108                status.as_kernel_param(),
2109                op_id.as_kernel_param(),
2110            ];
2111            // SAFETY: both probe variants have the same parameter ABI.
2112            unsafe {
2113                probe.clone().launch_on_stream(
2114                    stream,
2115                    launch_config(left_view.capacity),
2116                    &mut probe_params,
2117                )
2118            }
2119            .map_err(|error| XlogError::Kernel(format!("resident join probe launch: {error}")))
2120        };
2121        launch_probe(0)?;
2122        self.record_resident_finalize_on_stream(
2123            "resident_join_finalize",
2124            required,
2125            output_view,
2126            status,
2127            op_id,
2128            stream,
2129        )?;
2130        memset_u8_async(
2131            workspace.required.device_ptr_value(),
2132            0,
2133            8,
2134            stream,
2135            "join materialization required",
2136        )?;
2137        launch_probe(1)
2138    }
2139
2140    #[allow(clippy::too_many_arguments)]
2141    fn record_resident_finalize_on_stream(
2142        &self,
2143        kernel: &str,
2144        required: u64,
2145        output: ResidentRelationView,
2146        status: u64,
2147        op_id: u32,
2148        stream: &CudaStream,
2149    ) -> Result<()> {
2150        let function = self
2151            .device()
2152            .inner()
2153            .get_func(MODULE, kernel)
2154            .ok_or_else(|| XlogError::Kernel(format!("{kernel} kernel missing")))?;
2155        let mut params = vec![
2156            required.as_kernel_param(),
2157            output.as_kernel_param(),
2158            status.as_kernel_param(),
2159            op_id.as_kernel_param(),
2160        ];
2161        // SAFETY: set and join finalize share this exact parameter ABI.
2162        unsafe {
2163            function
2164                .clone()
2165                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
2166        }
2167        .map_err(|error| XlogError::Kernel(format!("resident finalize launch: {error}")))
2168    }
2169
2170    /// Enqueue the per-iteration whole-SCC changed aggregate reset.
2171    pub fn record_resident_changed_reset_on_stream(
2172        &self,
2173        control: &ResidentConvergenceControl,
2174        stream: &CudaStream,
2175    ) -> Result<()> {
2176        let function = self
2177            .device()
2178            .inner()
2179            .get_func(MODULE, "resident_changed_reset")
2180            .ok_or_else(|| XlogError::Kernel("resident_changed_reset kernel missing".into()))?;
2181        let changed = control.changed.device_ptr_value();
2182        let mut params = vec![changed.as_kernel_param()];
2183        // SAFETY: parameter list exactly matches resident_changed_reset.
2184        unsafe {
2185            function
2186                .clone()
2187                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
2188        }
2189        .map_err(|error| XlogError::Kernel(format!("resident changed reset launch: {error}")))
2190    }
2191
2192    /// Atomically fold one recursive head's novel count into the SCC aggregate.
2193    pub fn record_resident_changed_mark_on_stream(
2194        &self,
2195        novel_count: &TrackedCudaSlice<u32>,
2196        control: &ResidentConvergenceControl,
2197        stream: &CudaStream,
2198    ) -> Result<()> {
2199        if novel_count.is_empty() {
2200            return Err(XlogError::Kernel(
2201                "resident changed mark requires one device count".into(),
2202            ));
2203        }
2204        let function = self
2205            .device()
2206            .inner()
2207            .get_func(MODULE, "resident_changed_mark")
2208            .ok_or_else(|| XlogError::Kernel("resident_changed_mark kernel missing".into()))?;
2209        let novel = novel_count.device_ptr_value();
2210        let changed = control.changed.device_ptr_value();
2211        let mut params = vec![novel.as_kernel_param(), changed.as_kernel_param()];
2212        // SAFETY: parameter list exactly matches resident_changed_mark.
2213        unsafe {
2214            function
2215                .clone()
2216                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
2217        }
2218        .map_err(|error| XlogError::Kernel(format!("resident changed mark launch: {error}")))
2219    }
2220
2221    /// Enqueue the single WHILE-tail device convergence decision.
2222    pub fn record_resident_convergence_on_stream(
2223        &self,
2224        conditional_handle: u64,
2225        iteration_limit: u32,
2226        op_id: u32,
2227        control: &ResidentConvergenceControl,
2228        stream: &CudaStream,
2229    ) -> Result<()> {
2230        if iteration_limit == 0 {
2231            return Err(XlogError::Kernel(
2232                "resident convergence requires a positive iteration limit".into(),
2233            ));
2234        }
2235        let function = self
2236            .device()
2237            .inner()
2238            .get_func(MODULE, "resident_convergence")
2239            .ok_or_else(|| XlogError::Kernel("resident_convergence kernel missing".into()))?;
2240        let status = control.status.device_ptr_value();
2241        let changed = control.changed.device_ptr_value();
2242        let loop_iterations = control.loop_iterations.device_ptr_value();
2243        let mut params = vec![
2244            conditional_handle.as_kernel_param(),
2245            iteration_limit.as_kernel_param(),
2246            op_id.as_kernel_param(),
2247            status.as_kernel_param(),
2248            changed.as_kernel_param(),
2249            loop_iterations.as_kernel_param(),
2250        ];
2251        // SAFETY: parameter list exactly matches resident_convergence.
2252        unsafe {
2253            function
2254                .clone()
2255                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
2256        }
2257        .map_err(|error| XlogError::Kernel(format!("resident convergence launch: {error}")))
2258    }
2259
2260    /// Publish transaction Success after every parent segment and SCC completes.
2261    pub fn record_resident_terminal_success_on_stream(
2262        &self,
2263        op_id: u32,
2264        control: &ResidentConvergenceControl,
2265        stream: &CudaStream,
2266    ) -> Result<()> {
2267        let function = self
2268            .device()
2269            .inner()
2270            .get_func(MODULE, "resident_terminal_success")
2271            .ok_or_else(|| XlogError::Kernel("resident_terminal_success kernel missing".into()))?;
2272        let status = control.status.device_ptr_value();
2273        let mut params = vec![op_id.as_kernel_param(), status.as_kernel_param()];
2274        // SAFETY: parameter list exactly matches resident_terminal_success.
2275        unsafe {
2276            function
2277                .clone()
2278                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
2279        }
2280        .map_err(|error| XlogError::Kernel(format!("resident terminal success launch: {error}")))
2281    }
2282
2283    /// Enqueue a device-written terminal status for execution-path tests.
2284    ///
2285    /// The kernel uses the same first-terminal-status publication protocol as
2286    /// production failures; this API never copies a status record to device.
2287    #[doc(hidden)]
2288    pub fn record_resident_test_status_on_stream(
2289        &self,
2290        control: &ResidentConvergenceControl,
2291        injected: ResidentTerminalStatus,
2292        stream: &CudaStream,
2293    ) -> Result<()> {
2294        if !matches!(
2295            injected.code,
2296            code if code == ResidentTerminalCode::Success as u32
2297                || code == ResidentTerminalCode::IterationLimit as u32
2298                || code == ResidentTerminalCode::CapacityOverflow as u32
2299                || code == ResidentTerminalCode::ResourceExhausted as u32
2300        ) {
2301            return Err(XlogError::Kernel(format!(
2302                "resident test status code {} is not terminal",
2303                injected.code
2304            )));
2305        }
2306        let function = self
2307            .device()
2308            .inner()
2309            .get_func(MODULE, "resident_test_status")
2310            .ok_or_else(|| XlogError::Kernel("resident_test_status kernel missing".into()))?;
2311        let code = injected.code;
2312        let op_id = injected.op_id;
2313        let resource_code = injected.resource_code;
2314        let iterations = injected.iterations;
2315        let limit = injected.limit;
2316        let required = injected.required;
2317        let capacity = injected.capacity;
2318        let status = control.status.device_ptr_value();
2319        let mut params = vec![
2320            code.as_kernel_param(),
2321            op_id.as_kernel_param(),
2322            resource_code.as_kernel_param(),
2323            iterations.as_kernel_param(),
2324            limit.as_kernel_param(),
2325            required.as_kernel_param(),
2326            capacity.as_kernel_param(),
2327            status.as_kernel_param(),
2328        ];
2329        // SAFETY: parameter list exactly matches resident_test_status.
2330        unsafe {
2331            function
2332                .clone()
2333                .launch_on_stream(stream, LaunchConfig::for_num_elems(1), &mut params)
2334        }
2335        .map_err(|error| XlogError::Kernel(format!("resident test status launch: {error}")))
2336    }
2337}
2338
2339#[cfg(test)]
2340mod tests {
2341    use std::sync::Arc;
2342
2343    use xlog_core::{MemoryBudget, ScalarType, Schema};
2344
2345    use crate::{cuda_graph::CapturedCudaGraph, CudaDevice, GpuMemoryManager};
2346
2347    use super::{
2348        checked_hash_slot_capacity, checked_receipt_pointee, record_receipt_pointee_uses,
2349        resident_device_trace_bytes, resident_packed_receipt_with_schema_winners_device_bytes,
2350        resident_schema_winners_device_bytes, validate_receipt_pointee_owners,
2351        validate_receipt_pointee_ranges, validate_receipt_schedule_block_mapping,
2352        validate_receipt_schedule_mapping, ResidentJoinKind, ResidentPackedReceipt,
2353        ResidentReceiptPointee, ResidentReceiptPointeeRole, ResidentResourceCode,
2354        ResidentTerminalCode, ResidentTerminalStatus,
2355    };
2356
2357    #[test]
2358    fn dual_device_trace_and_packed_receipt_have_exact_bytes() {
2359        assert_eq!(resident_device_trace_bytes(), 16);
2360        assert_eq!(
2361            resident_packed_receipt_with_schema_winners_device_bytes(3).unwrap(),
2362            164
2363        );
2364    }
2365
2366    #[test]
2367    fn schema_winners_retain_immutable_host_defaults_for_replay() {
2368        let _: fn(&super::ResidentSchemaWinners) -> &[u32] =
2369            super::ResidentSchemaWinners::default_schema_ids;
2370    }
2371
2372    #[test]
2373    fn receipt_pointee_manifest_rejects_overlap_and_checked_range_overflow() {
2374        let block = crate::device_runtime::BlockId {
2375            ptr: 0x1000,
2376            generation: crate::device_runtime::Generation(3),
2377            alloc_stream: crate::device_runtime::StreamId(4),
2378            device_ordinal: 0,
2379        };
2380        let relation = checked_receipt_pointee(
2381            ResidentReceiptPointeeRole::RelationCount(0),
2382            0x1000,
2383            7,
2384            Some((block, 16)),
2385        )
2386        .expect("valid relation count");
2387        let overlapping = checked_receipt_pointee(
2388            ResidentReceiptPointeeRole::ScanTrace,
2389            0x1002,
2390            7,
2391            Some((block, 16)),
2392        )
2393        .expect("individually valid overlapping counter");
2394        assert!(validate_receipt_pointee_ranges(&[relation, overlapping]).is_err());
2395
2396        assert!(checked_receipt_pointee(
2397            ResidentReceiptPointeeRole::FilterTrace,
2398            u64::MAX - 1,
2399            7,
2400            None,
2401        )
2402        .is_err());
2403        assert!(checked_receipt_pointee(
2404            ResidentReceiptPointeeRole::FilterTrace,
2405            0x100e,
2406            7,
2407            Some((block, 16)),
2408        )
2409        .is_err());
2410    }
2411
2412    #[test]
2413    fn receipt_manifest_mapping_requires_exact_role_and_pointer_order() {
2414        let zero_heads = [
2415            checked_receipt_pointee(ResidentReceiptPointeeRole::ScanTrace, 0x800, 7, None).unwrap(),
2416            checked_receipt_pointee(ResidentReceiptPointeeRole::FilterTrace, 0x810, 7, None)
2417                .unwrap(),
2418            checked_receipt_pointee(
2419                ResidentReceiptPointeeRole::SemanticScanTrace,
2420                0x820,
2421                7,
2422                None,
2423            )
2424            .unwrap(),
2425            checked_receipt_pointee(
2426                ResidentReceiptPointeeRole::SemanticFilterTrace,
2427                0x830,
2428                7,
2429                None,
2430            )
2431            .unwrap(),
2432        ];
2433        validate_receipt_schedule_mapping(&zero_heads, &[], [0x800, 0x810, 0x820, 0x830], &[])
2434            .expect("zero-head receipt mapping");
2435
2436        let one_head = [
2437            checked_receipt_pointee(ResidentReceiptPointeeRole::RelationCount(0), 0x900, 7, None)
2438                .unwrap(),
2439            checked_receipt_pointee(ResidentReceiptPointeeRole::ScanTrace, 0x910, 7, None).unwrap(),
2440            checked_receipt_pointee(ResidentReceiptPointeeRole::FilterTrace, 0x920, 7, None)
2441                .unwrap(),
2442            checked_receipt_pointee(
2443                ResidentReceiptPointeeRole::SemanticScanTrace,
2444                0x930,
2445                7,
2446                None,
2447            )
2448            .unwrap(),
2449            checked_receipt_pointee(
2450                ResidentReceiptPointeeRole::SemanticFilterTrace,
2451                0x940,
2452                7,
2453                None,
2454            )
2455            .unwrap(),
2456            checked_receipt_pointee(ResidentReceiptPointeeRole::SchemaWinner(0), 0x950, 7, None)
2457                .unwrap(),
2458        ];
2459        validate_receipt_schedule_mapping(
2460            &one_head,
2461            &[0x900],
2462            [0x910, 0x920, 0x930, 0x940],
2463            &[0x950],
2464        )
2465        .expect("one-head receipt mapping");
2466
2467        let entries = [
2468            (ResidentReceiptPointeeRole::RelationCount(0), 0x1000),
2469            (ResidentReceiptPointeeRole::RelationCount(1), 0x1010),
2470            (ResidentReceiptPointeeRole::ScanTrace, 0x1020),
2471            (ResidentReceiptPointeeRole::FilterTrace, 0x1030),
2472            (ResidentReceiptPointeeRole::SemanticScanTrace, 0x1040),
2473            (ResidentReceiptPointeeRole::SemanticFilterTrace, 0x1050),
2474            (ResidentReceiptPointeeRole::SchemaWinner(0), 0x1060),
2475            (ResidentReceiptPointeeRole::SchemaWinner(1), 0x1070),
2476        ]
2477        .map(|(role, ptr)| checked_receipt_pointee(role, ptr, 7, None).unwrap());
2478
2479        validate_receipt_schedule_mapping(
2480            &entries,
2481            &[0x1000, 0x1010],
2482            [0x1020, 0x1030, 0x1040, 0x1050],
2483            &[0x1060, 0x1070],
2484        )
2485        .expect("exact receipt mapping");
2486        assert!(validate_receipt_schedule_mapping(
2487            &entries,
2488            &[0x1010, 0x1000],
2489            [0x1020, 0x1030, 0x1040, 0x1050],
2490            &[0x1060, 0x1070],
2491        )
2492        .is_err());
2493        assert!(validate_receipt_schedule_mapping(
2494            &entries,
2495            &[0x1000, 0x1010],
2496            [0x1020, 0x1030, 0x1040, 0x1050],
2497            &[0x1060, 0x9999],
2498        )
2499        .is_err());
2500    }
2501
2502    #[test]
2503    fn additive_receipt_manifest_requires_exact_manager_and_runtime_block_owner() {
2504        let block = crate::device_runtime::BlockId {
2505            ptr: 0x2000,
2506            generation: crate::device_runtime::Generation(1),
2507            alloc_stream: crate::device_runtime::StreamId(2),
2508            device_ordinal: 3,
2509        };
2510        let owned = checked_receipt_pointee(
2511            ResidentReceiptPointeeRole::RelationCount(0),
2512            0x2000,
2513            17,
2514            Some((block, 8)),
2515        )
2516        .unwrap();
2517        validate_receipt_pointee_owners(&[owned], 17, 3).expect("exact owner");
2518
2519        let foreign_manager = checked_receipt_pointee(
2520            ResidentReceiptPointeeRole::RelationCount(0),
2521            0x2000,
2522            99,
2523            Some((block, 8)),
2524        )
2525        .unwrap();
2526        assert!(validate_receipt_pointee_owners(&[foreign_manager], 17, 3).is_err());
2527        let untracked = checked_receipt_pointee(
2528            ResidentReceiptPointeeRole::RelationCount(0),
2529            0x2000,
2530            17,
2531            None,
2532        )
2533        .unwrap();
2534        assert!(validate_receipt_pointee_owners(&[untracked], 17, 3).is_err());
2535        assert!(validate_receipt_pointee_owners(&[owned], 17, 4).is_err());
2536    }
2537
2538    #[test]
2539    fn receipt_schedule_mapping_requires_full_block_identity() {
2540        let owned = crate::device_runtime::BlockId {
2541            ptr: 0x2000,
2542            generation: crate::device_runtime::Generation(1),
2543            alloc_stream: crate::device_runtime::StreamId(2),
2544            device_ordinal: 3,
2545        };
2546        let different_stream = crate::device_runtime::BlockId {
2547            alloc_stream: crate::device_runtime::StreamId(9),
2548            ..owned
2549        };
2550        let pointee = checked_receipt_pointee(
2551            ResidentReceiptPointeeRole::RelationCount(0),
2552            0x2000,
2553            17,
2554            Some((owned, 8)),
2555        )
2556        .unwrap();
2557
2558        validate_receipt_schedule_block_mapping(&[pointee], &[owned])
2559            .expect("exact allocation generation and stream");
2560        assert!(validate_receipt_schedule_block_mapping(&[pointee], &[different_stream]).is_err());
2561    }
2562
2563    #[test]
2564    fn packed_receipt_retains_immutable_pointee_manifest() {
2565        let _: fn(&ResidentPackedReceipt) -> &[ResidentReceiptPointee] =
2566            ResidentPackedReceipt::pointee_manifest;
2567    }
2568
2569    #[test]
2570    fn receipt_manifest_records_every_runtime_pointee_as_read() {
2571        let first = crate::device_runtime::BlockId {
2572            ptr: 0x3000,
2573            generation: crate::device_runtime::Generation(1),
2574            alloc_stream: crate::device_runtime::StreamId(2),
2575            device_ordinal: 0,
2576        };
2577        let second = crate::device_runtime::BlockId {
2578            ptr: 0x4000,
2579            ..first
2580        };
2581        let pointees = [
2582            checked_receipt_pointee(
2583                ResidentReceiptPointeeRole::ScanTrace,
2584                first.ptr,
2585                17,
2586                Some((first, 4)),
2587            )
2588            .unwrap(),
2589            checked_receipt_pointee(
2590                ResidentReceiptPointeeRole::FilterTrace,
2591                second.ptr,
2592                17,
2593                Some((second, 4)),
2594            )
2595            .unwrap(),
2596        ];
2597        let mut recorder =
2598            crate::launch::LaunchRecorder::new_strict(crate::device_runtime::StreamId(8));
2599
2600        record_receipt_pointee_uses(&pointees, &mut recorder);
2601
2602        assert_eq!(recorder.recorded_count(), 2);
2603    }
2604
2605    #[test]
2606    fn relational_external_owners_expose_complete_allocation_snapshots() {
2607        type Owner = Option<crate::memory::RuntimeAllocationIdentity>;
2608        let _: fn(&super::ResidentSetWorkspace) -> xlog_core::Result<[Owner; 2]> =
2609            super::ResidentSetWorkspace::schedule_owner_snapshots;
2610        let _: fn(&super::ResidentJoinWorkspace) -> xlog_core::Result<[Owner; 3]> =
2611            super::ResidentJoinWorkspace::schedule_owner_snapshots;
2612        let _: fn(&super::ResidentConvergenceControl) -> xlog_core::Result<[Owner; 3]> =
2613            super::ResidentConvergenceControl::schedule_owner_snapshots;
2614        let _: fn(&super::ResidentDeviceTrace) -> xlog_core::Result<[Owner; 4]> =
2615            super::ResidentDeviceTrace::schedule_owner_snapshots;
2616        let _: fn(&super::ResidentSchemaWinners) -> xlog_core::Result<[Owner; 2]> =
2617            super::ResidentSchemaWinners::schedule_owner_snapshots;
2618        let _: fn(&ResidentPackedReceipt) -> xlog_core::Result<[Owner; 2]> =
2619            ResidentPackedReceipt::schedule_owner_snapshots;
2620    }
2621
2622    fn provider() -> Option<super::CudaKernelProvider> {
2623        let device = match CudaDevice::new(0) {
2624            Ok(device) => Arc::new(device),
2625            Err(error) if std::env::var("XLOG_REQUIRE_CUDA").as_deref() == Ok("1") => {
2626                panic!("XLOG_REQUIRE_CUDA=1 but CUDA device initialization failed: {error}")
2627            }
2628            Err(error) => {
2629                eprintln!("Skipping resident CUDA test: {error}");
2630                return None;
2631            }
2632        };
2633        let memory = Arc::new(GpuMemoryManager::new(
2634            Arc::clone(&device),
2635            MemoryBudget::with_limit(512 * 1024 * 1024),
2636        ));
2637        match super::CudaKernelProvider::new(device, memory) {
2638            Ok(provider) => Some(provider),
2639            Err(error) if std::env::var("XLOG_REQUIRE_CUDA").as_deref() == Ok("1") => {
2640                panic!("XLOG_REQUIRE_CUDA=1 but resident provider setup failed: {error}")
2641            }
2642            Err(error) => {
2643                eprintln!("Skipping resident CUDA test: {error}");
2644                None
2645            }
2646        }
2647    }
2648
2649    fn pair_schema() -> Schema {
2650        Schema::new(vec![
2651            ("key".to_string(), ScalarType::Symbol),
2652            ("value".to_string(), ScalarType::U64),
2653        ])
2654    }
2655
2656    fn pair_buffer(
2657        provider: &super::CudaKernelProvider,
2658        keys: &[u32],
2659        values: &[u64],
2660    ) -> crate::CudaBuffer {
2661        let key_bytes: Vec<u8> = keys.iter().flat_map(|value| value.to_le_bytes()).collect();
2662        let value_bytes: Vec<u8> = values
2663            .iter()
2664            .flat_map(|value| value.to_le_bytes())
2665            .collect();
2666        provider
2667            .create_buffer_from_slices(&[&key_bytes, &value_bytes], pair_schema())
2668            .expect("pair buffer")
2669    }
2670
2671    #[test]
2672    fn terminal_status_wire_layout_is_stable() {
2673        assert_eq!(std::mem::size_of::<ResidentTerminalStatus>(), 40);
2674        assert_eq!(std::mem::align_of::<ResidentTerminalStatus>(), 8);
2675        assert_eq!(ResidentTerminalCode::Running as u32, 0);
2676        assert_eq!(ResidentTerminalCode::Success as u32, 1);
2677        assert_eq!(ResidentTerminalCode::IterationLimit as u32, 2);
2678        assert_eq!(ResidentTerminalCode::CapacityOverflow as u32, 3);
2679        assert_eq!(ResidentTerminalCode::ResourceExhausted as u32, 4);
2680        assert_eq!(ResidentResourceCode::SetHashSlots as u32, 1);
2681        assert_eq!(ResidentResourceCode::JoinBuckets as u32, 2);
2682        assert_eq!(ResidentResourceCode::JoinChains as u32, 3);
2683        assert_eq!(ResidentResourceCode::InputRows as u32, 4);
2684        assert_eq!(ResidentResourceCode::OutputRows as u32, 5);
2685    }
2686
2687    #[test]
2688    fn hash_capacity_is_checked_and_keeps_load_at_most_one_half() {
2689        assert_eq!(checked_hash_slot_capacity(0).unwrap(), 1);
2690        assert_eq!(checked_hash_slot_capacity(1).unwrap(), 2);
2691        assert_eq!(checked_hash_slot_capacity(4_994).unwrap(), 16_384);
2692        assert_eq!(checked_hash_slot_capacity(9_988).unwrap(), 32_768);
2693        assert!(checked_hash_slot_capacity(u64::from(u32::MAX)).is_err());
2694    }
2695
2696    #[test]
2697    fn schema_winner_workspace_bytes_are_exact_and_checked() {
2698        assert_eq!(resident_schema_winners_device_bytes(0).unwrap(), 8);
2699        assert_eq!(resident_schema_winners_device_bytes(1).unwrap(), 8);
2700        assert_eq!(resident_schema_winners_device_bytes(3).unwrap(), 24);
2701        assert!(resident_schema_winners_device_bytes(usize::MAX).is_err());
2702    }
2703
2704    #[test]
2705    fn real_cuda_schema_winner_uses_first_nonempty_and_packs_one_receipt() {
2706        let Some(provider) = provider() else { return };
2707        let mut empty = provider
2708            .prepare_resident_relation(pair_schema(), 1)
2709            .expect("empty relation");
2710        provider
2711            .initialize_resident_relation_count(&mut empty, 0)
2712            .expect("empty count");
2713        let mut nonempty = provider
2714            .prepare_resident_relation(pair_schema(), 1)
2715            .expect("nonempty relation");
2716        provider
2717            .initialize_resident_relation_count(&mut nonempty, 1)
2718            .expect("nonempty count");
2719        let mut all_empty = provider
2720            .prepare_resident_relation(pair_schema(), 1)
2721            .expect("all-empty relation");
2722        provider
2723            .initialize_resident_relation_count(&mut all_empty, 0)
2724            .expect("all-empty count");
2725        let control = provider
2726            .prepare_resident_convergence_control()
2727            .expect("control");
2728        let trace = provider.prepare_resident_device_trace().expect("trace");
2729        let winners = provider
2730            .prepare_resident_schema_winners(&[11, 22, 33])
2731            .expect("schema winners");
2732        let receipt = provider
2733            .prepare_resident_packed_receipt_with_trace_and_schema_winners(
2734                &[&empty, &nonempty, &all_empty],
2735                &trace,
2736                &winners,
2737            )
2738            .expect("receipt");
2739        let mut pinned = provider
2740            .prepare_resident_pinned_receipt(&receipt)
2741            .expect("pinned receipt");
2742        let stream = provider
2743            .device()
2744            .inner()
2745            .stream()
2746            .context()
2747            .new_stream()
2748            .expect("stream");
2749        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
2750            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
2751            provider.record_resident_device_trace_initialize_on_stream(&trace, &stream)?;
2752            provider
2753                .record_resident_schema_winners_initialize_on_stream(&winners, &receipt, &stream)?;
2754            provider.record_resident_schema_winner_mark_on_stream(
2755                empty.num_rows_device(),
2756                &winners,
2757                0,
2758                33,
2759                &stream,
2760            )?;
2761            provider.record_resident_schema_winner_mark_on_stream(
2762                nonempty.num_rows_device(),
2763                &winners,
2764                0,
2765                44,
2766                &stream,
2767            )?;
2768            provider.record_resident_schema_winner_mark_on_stream(
2769                all_empty.num_rows_device(),
2770                &winners,
2771                2,
2772                88,
2773                &stream,
2774            )?;
2775            provider.record_resident_schema_winner_mark_on_stream(
2776                nonempty.num_rows_device(),
2777                &winners,
2778                0,
2779                55,
2780                &stream,
2781            )?;
2782            provider.record_resident_schema_winner_mark_on_stream(
2783                empty.num_rows_device(),
2784                &winners,
2785                1,
2786                66,
2787                &stream,
2788            )?;
2789            provider.record_resident_schema_winner_mark_on_stream(
2790                nonempty.num_rows_device(),
2791                &winners,
2792                1,
2793                77,
2794                &stream,
2795            )?;
2796            provider.record_resident_terminal_success_on_stream(91, &control, &stream)?;
2797            provider.record_resident_receipt_pack_on_stream(&control, &receipt, &stream)
2798        })
2799        .expect("capture schema winners");
2800
2801        provider.reset_host_transfer_stats();
2802        provider.reset_d2h_transfer_count();
2803        provider.reset_untracked_metadata_dtoh_count();
2804        provider.reset_final_observation_transfer_stats();
2805        graph.launch(&stream).expect("launch schema winners");
2806        stream.synchronize().expect("schema winner core sync");
2807        let ordinary = provider.host_transfer_stats();
2808        let launch_metadata = provider.host_launch_metadata_transfer_stats();
2809        assert_eq!(ordinary.htod_calls, 0);
2810        assert_eq!(ordinary.htod_bytes, 0);
2811        assert_eq!(ordinary.dtoh_calls, 0);
2812        assert_eq!(ordinary.dtoh_bytes, 0);
2813        assert_eq!(launch_metadata.htod_calls, 0);
2814        assert_eq!(launch_metadata.htod_bytes, 0);
2815        assert_eq!(provider.d2h_transfer_count(), 0);
2816        assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
2817
2818        let bytes = provider
2819            .observe_resident_packed_receipt(&receipt, &mut pinned, &stream)
2820            .expect("observe schema winners");
2821        assert_eq!(receipt.relation_count_len(), 3);
2822        assert_eq!(receipt.device_trace_field_count(), 4);
2823        assert_eq!(receipt.schema_winner_count(), 3);
2824        assert_eq!(receipt.total_count_field_len(), 10);
2825        assert_eq!(receipt.len_bytes(), 84);
2826        assert_eq!(u32::from_ne_bytes(bytes[72..76].try_into().unwrap()), 44);
2827        assert_eq!(u32::from_ne_bytes(bytes[76..80].try_into().unwrap()), 22);
2828        assert_eq!(u32::from_ne_bytes(bytes[80..84].try_into().unwrap()), 33);
2829        let final_observation = provider.final_observation_transfer_stats();
2830        assert_eq!(final_observation.dtoh_calls, 1);
2831        assert_eq!(final_observation.dtoh_bytes, 84);
2832        assert_eq!(final_observation.pinned_receipts, 1);
2833    }
2834
2835    #[test]
2836    fn real_cuda_relation_count_initialization_is_checked_and_cache_free() {
2837        let Some(provider) = provider() else { return };
2838        let mut relation = provider
2839            .prepare_resident_relation(pair_schema(), 1)
2840            .expect("relation");
2841
2842        provider
2843            .initialize_resident_relation_count(&mut relation, 1)
2844            .expect("singleton initialization");
2845        assert_eq!(relation.buffer().cached_row_count(), None);
2846        assert_eq!(
2847            provider
2848                .device()
2849                .inner()
2850                .dtoh_sync_copy(relation.num_rows_device())
2851                .expect("singleton count"),
2852            vec![1]
2853        );
2854
2855        provider
2856            .initialize_resident_relation_count(&mut relation, 0)
2857            .expect("empty initialization");
2858        assert_eq!(relation.buffer().cached_row_count(), None);
2859        assert_eq!(
2860            provider
2861                .device()
2862                .inner()
2863                .dtoh_sync_copy(relation.num_rows_device())
2864                .expect("empty count"),
2865            vec![0]
2866        );
2867
2868        let error = provider
2869            .initialize_resident_relation_count(&mut relation, 2)
2870            .expect_err("non-set count must be rejected");
2871        assert!(error.to_string().contains("expected 0 or 1"));
2872        assert_eq!(relation.buffer().cached_row_count(), None);
2873    }
2874
2875    #[test]
2876    fn real_cuda_relation_count_clear_is_graph_capturable_on_supplied_stream() {
2877        let Some(provider) = provider() else { return };
2878        let mut relation = provider
2879            .prepare_resident_relation(pair_schema(), 1)
2880            .expect("relation");
2881        provider
2882            .initialize_resident_relation_count(&mut relation, 1)
2883            .expect("singleton initialization");
2884        let stream = provider
2885            .device()
2886            .inner()
2887            .stream()
2888            .context()
2889            .new_stream()
2890            .expect("non-default stream");
2891
2892        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
2893            provider.record_resident_relation_clear_on_stream(&relation, &stream)
2894        })
2895        .expect("capture relation clear");
2896        graph.launch(&stream).expect("launch relation clear");
2897        stream.synchronize().expect("relation clear sync");
2898
2899        assert_eq!(
2900            provider
2901                .device()
2902                .inner()
2903                .dtoh_sync_copy(relation.num_rows_device())
2904                .expect("cleared count"),
2905            vec![0]
2906        );
2907        assert_eq!(relation.buffer().cached_row_count(), None);
2908    }
2909
2910    #[test]
2911    fn real_cuda_final_receipt_uses_one_pinned_dtoh_without_ordinary_counters() {
2912        let Some(provider) = provider() else { return };
2913        let mut relation = provider
2914            .prepare_resident_relation(pair_schema(), 1)
2915            .expect("relation");
2916        provider
2917            .initialize_resident_relation_count(&mut relation, 1)
2918            .expect("singleton initialization");
2919        let control = provider
2920            .prepare_resident_convergence_control()
2921            .expect("control");
2922        let receipt = provider
2923            .prepare_resident_packed_receipt(&[&relation])
2924            .expect("device receipt");
2925        let mut pinned = provider
2926            .prepare_resident_pinned_receipt(&receipt)
2927            .expect("pinned receipt");
2928        assert_eq!(pinned.len_bytes(), receipt.len_bytes());
2929        let stream = provider
2930            .device()
2931            .inner()
2932            .stream()
2933            .context()
2934            .new_stream()
2935            .expect("non-default stream");
2936        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
2937            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
2938            provider.record_resident_terminal_success_on_stream(77, &control, &stream)?;
2939            provider.record_resident_receipt_pack_on_stream(&control, &receipt, &stream)
2940        })
2941        .expect("capture receipt production");
2942
2943        provider.reset_host_transfer_stats();
2944        provider.reset_d2h_transfer_count();
2945        provider.reset_untracked_metadata_dtoh_count();
2946        provider.reset_final_observation_transfer_stats();
2947        graph.launch(&stream).expect("launch receipt production");
2948        stream.synchronize().expect("core graph sync");
2949        let bytes = provider
2950            .observe_resident_packed_receipt(&receipt, &mut pinned, &stream)
2951            .expect("final receipt observation");
2952
2953        assert_eq!(bytes.len(), receipt.len_bytes());
2954        assert_eq!(u32::from_ne_bytes(bytes[0..4].try_into().unwrap()), 1);
2955        assert_eq!(u32::from_ne_bytes(bytes[4..8].try_into().unwrap()), 77);
2956        assert_eq!(u32::from_ne_bytes(bytes[40..44].try_into().unwrap()), 0);
2957        assert_eq!(u32::from_ne_bytes(bytes[44..48].try_into().unwrap()), 1);
2958        let ordinary = provider.host_transfer_stats();
2959        let launch_metadata = provider.host_launch_metadata_transfer_stats();
2960        assert_eq!(ordinary.htod_calls, 0);
2961        assert_eq!(ordinary.htod_bytes, 0);
2962        assert_eq!(ordinary.dtoh_calls, 0);
2963        assert_eq!(ordinary.dtoh_bytes, 0);
2964        assert_eq!(launch_metadata.htod_calls, 0);
2965        assert_eq!(launch_metadata.htod_bytes, 0);
2966        assert_eq!(provider.d2h_transfer_count(), 0);
2967        assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
2968        let final_observation = provider.final_observation_transfer_stats();
2969        assert_eq!(final_observation.dtoh_calls, 1);
2970        assert_eq!(final_observation.dtoh_bytes, receipt.len_bytes() as u64);
2971        assert_eq!(final_observation.pinned_receipts, 1);
2972    }
2973
2974    #[test]
2975    fn real_cuda_device_trace_packs_exact_invocation_counts_in_final_receipt() {
2976        let Some(provider) = provider() else { return };
2977        let mut relation = provider
2978            .prepare_resident_relation(pair_schema(), 1)
2979            .expect("relation");
2980        provider
2981            .initialize_resident_relation_count(&mut relation, 1)
2982            .expect("singleton initialization");
2983        let control = provider
2984            .prepare_resident_convergence_control()
2985            .expect("control");
2986        let trace = provider
2987            .prepare_resident_device_trace()
2988            .expect("device trace");
2989        let receipt = provider
2990            .prepare_resident_packed_receipt_with_trace(&[&relation], &trace)
2991            .expect("traced device receipt");
2992        let mut pinned = provider
2993            .prepare_resident_pinned_receipt(&receipt)
2994            .expect("pinned receipt");
2995        assert_eq!(receipt.relation_count_len(), 1);
2996        assert_eq!(receipt.device_trace_field_count(), 4);
2997        assert_eq!(receipt.total_count_field_len(), 5);
2998        assert_eq!(receipt.len_bytes(), 64);
2999        assert_eq!(pinned.len_bytes(), receipt.len_bytes());
3000        let stream = provider
3001            .device()
3002            .inner()
3003            .stream()
3004            .context()
3005            .new_stream()
3006            .expect("non-default stream");
3007        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
3008            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
3009            provider.record_resident_device_trace_initialize_on_stream(&trace, &stream)?;
3010            provider.record_resident_scan_trace_on_stream(&trace, &stream)?;
3011            provider.record_resident_scan_trace_on_stream(&trace, &stream)?;
3012            provider.record_resident_filter_trace_on_stream(&trace, &stream)?;
3013            provider.record_resident_scan_trace_on_stream(&trace, &stream)?;
3014            provider.record_resident_filter_trace_on_stream(&trace, &stream)?;
3015            provider.record_resident_terminal_success_on_stream(88, &control, &stream)?;
3016            provider.record_resident_receipt_pack_on_stream(&control, &receipt, &stream)
3017        })
3018        .expect("capture traced receipt production");
3019
3020        provider.reset_host_transfer_stats();
3021        provider.reset_d2h_transfer_count();
3022        provider.reset_untracked_metadata_dtoh_count();
3023        provider.reset_final_observation_transfer_stats();
3024        graph
3025            .launch(&stream)
3026            .expect("launch traced receipt production");
3027        stream.synchronize().expect("core graph sync");
3028        let ordinary_before_observation = provider.host_transfer_stats();
3029        let launch_metadata_before_observation = provider.host_launch_metadata_transfer_stats();
3030        assert_eq!(ordinary_before_observation.htod_calls, 0);
3031        assert_eq!(ordinary_before_observation.htod_bytes, 0);
3032        assert_eq!(ordinary_before_observation.dtoh_calls, 0);
3033        assert_eq!(ordinary_before_observation.dtoh_bytes, 0);
3034        assert_eq!(launch_metadata_before_observation.htod_calls, 0);
3035        assert_eq!(launch_metadata_before_observation.htod_bytes, 0);
3036        assert_eq!(provider.d2h_transfer_count(), 0);
3037        assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
3038
3039        let bytes = provider
3040            .observe_resident_packed_receipt(&receipt, &mut pinned, &stream)
3041            .expect("final traced receipt observation");
3042        assert_eq!(bytes.len(), receipt.len_bytes());
3043        assert_eq!(u32::from_ne_bytes(bytes[0..4].try_into().unwrap()), 1);
3044        assert_eq!(u32::from_ne_bytes(bytes[4..8].try_into().unwrap()), 88);
3045        assert_eq!(u32::from_ne_bytes(bytes[40..44].try_into().unwrap()), 0);
3046        assert_eq!(u32::from_ne_bytes(bytes[44..48].try_into().unwrap()), 1);
3047        assert_eq!(u32::from_ne_bytes(bytes[48..52].try_into().unwrap()), 3);
3048        assert_eq!(u32::from_ne_bytes(bytes[52..56].try_into().unwrap()), 2);
3049        assert_eq!(u32::from_ne_bytes(bytes[56..60].try_into().unwrap()), 3);
3050        assert_eq!(u32::from_ne_bytes(bytes[60..64].try_into().unwrap()), 2);
3051        let ordinary_after_observation = provider.host_transfer_stats();
3052        assert_eq!(ordinary_after_observation.htod_calls, 0);
3053        assert_eq!(ordinary_after_observation.dtoh_calls, 0);
3054        assert_eq!(provider.d2h_transfer_count(), 0);
3055        assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
3056        let final_observation = provider.final_observation_transfer_stats();
3057        assert_eq!(final_observation.dtoh_calls, 1);
3058        assert_eq!(final_observation.dtoh_bytes, receipt.len_bytes() as u64);
3059        assert_eq!(final_observation.pinned_receipts, 1);
3060    }
3061
3062    #[test]
3063    fn real_cuda_test_status_kernel_publishes_first_error_after_indexed_op() {
3064        let Some(provider) = provider() else { return };
3065        let input = pair_buffer(&provider, &[1], &[10]);
3066        let output = provider
3067            .prepare_resident_relation(pair_schema(), 1)
3068            .expect("output");
3069        let workspace = provider
3070            .prepare_resident_set_workspace(1)
3071            .expect("workspace");
3072        let control = provider
3073            .prepare_resident_convergence_control()
3074            .expect("control");
3075        let first = ResidentTerminalStatus {
3076            code: ResidentTerminalCode::ResourceExhausted as u32,
3077            op_id: 61,
3078            resource_code: ResidentResourceCode::SetHashSlots as u32,
3079            iterations: 2,
3080            limit: 9,
3081            reserved: u32::MAX,
3082            required: 33,
3083            capacity: 16,
3084        };
3085        let second = ResidentTerminalStatus {
3086            code: ResidentTerminalCode::CapacityOverflow as u32,
3087            op_id: 62,
3088            resource_code: ResidentResourceCode::OutputRows as u32,
3089            iterations: 3,
3090            limit: 10,
3091            reserved: 0,
3092            required: 65,
3093            capacity: 32,
3094        };
3095        let stream = provider
3096            .device()
3097            .inner()
3098            .stream()
3099            .context()
3100            .new_stream()
3101            .expect("non-default stream");
3102        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
3103            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
3104            provider.record_resident_dedup_on_stream(
3105                &input, &output, &workspace, &control, 61, &stream,
3106            )?;
3107            provider.record_resident_test_status_on_stream(&control, first, &stream)?;
3108            provider.record_resident_test_status_on_stream(&control, second, &stream)?;
3109            provider.record_resident_terminal_success_on_stream(63, &control, &stream)
3110        })
3111        .expect("capture injected device status");
3112
3113        provider.reset_host_transfer_stats();
3114        graph
3115            .launch(&stream)
3116            .expect("launch injected device status");
3117        stream.synchronize().expect("injected status sync");
3118        let observed = provider
3119            .device()
3120            .inner()
3121            .dtoh_sync_copy(control.status_device())
3122            .expect("status observation")[0];
3123
3124        assert_eq!(
3125            observed,
3126            ResidentTerminalStatus {
3127                reserved: 0,
3128                ..first
3129            }
3130        );
3131        let transfers = provider.host_transfer_stats();
3132        let launch_metadata = provider.host_launch_metadata_transfer_stats();
3133        assert_eq!(transfers.htod_calls, 0);
3134        assert_eq!(transfers.htod_bytes, 0);
3135        assert_eq!(launch_metadata.htod_calls, 0);
3136        assert_eq!(launch_metadata.htod_bytes, 0);
3137    }
3138
3139    #[test]
3140    fn real_cuda_graph_union_deduplicates_full_mixed_width_rows() {
3141        let Some(provider) = provider() else { return };
3142        let left = pair_buffer(&provider, &[1, 1, 2], &[10, 10, 20]);
3143        let right = pair_buffer(&provider, &[2, 3], &[20, 30]);
3144        let output = provider
3145            .prepare_resident_relation(pair_schema(), 3)
3146            .expect("output");
3147        let workspace = provider
3148            .prepare_resident_set_workspace(5)
3149            .expect("workspace");
3150        let control = provider
3151            .prepare_resident_convergence_control()
3152            .expect("control");
3153        let stream = provider
3154            .device()
3155            .inner()
3156            .stream()
3157            .context()
3158            .new_stream()
3159            .expect("non-default stream");
3160        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
3161            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
3162            provider.record_resident_union_on_stream(
3163                &left, &right, &output, &workspace, &control, 7, &stream,
3164            )
3165        })
3166        .expect("capture resident union");
3167        graph.launch(&stream).expect("launch resident union");
3168        stream.synchronize().expect("resident union sync");
3169        let keys = provider
3170            .download_column::<u32>(output.buffer(), 0)
3171            .expect("keys");
3172        let values = provider
3173            .download_column::<u64>(output.buffer(), 1)
3174            .expect("values");
3175        let mut rows: Vec<_> = keys.into_iter().zip(values).collect();
3176        rows.sort_unstable();
3177        assert_eq!(rows, vec![(1, 10), (2, 20), (3, 30)]);
3178    }
3179
3180    #[test]
3181    fn real_cuda_graph_diff_compares_and_deduplicates_full_rows() {
3182        let Some(provider) = provider() else { return };
3183        let left = pair_buffer(&provider, &[1, 1, 1, 2], &[10, 10, 11, 20]);
3184        let right = pair_buffer(&provider, &[1, 9], &[10, 90]);
3185        let output = provider
3186            .prepare_resident_relation(pair_schema(), 2)
3187            .expect("output");
3188        let workspace = provider
3189            .prepare_resident_set_workspace(6)
3190            .expect("workspace");
3191        let control = provider
3192            .prepare_resident_convergence_control()
3193            .expect("control");
3194        let stream = provider
3195            .device()
3196            .inner()
3197            .stream()
3198            .context()
3199            .new_stream()
3200            .expect("non-default stream");
3201        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
3202            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
3203            provider.record_resident_diff_on_stream(
3204                &left, &right, &output, &workspace, &control, 10, &stream,
3205            )
3206        })
3207        .expect("capture resident diff");
3208        graph.launch(&stream).expect("launch resident diff");
3209        stream.synchronize().expect("resident diff sync");
3210        let keys = provider
3211            .download_column::<u32>(output.buffer(), 0)
3212            .expect("keys");
3213        let values = provider
3214            .download_column::<u64>(output.buffer(), 1)
3215            .expect("values");
3216        let mut rows: Vec<_> = keys.into_iter().zip(values).collect();
3217        rows.sort_unstable();
3218        assert_eq!(rows, vec![(1, 11), (2, 20)]);
3219    }
3220
3221    #[test]
3222    fn real_cuda_nullary_union_implements_unit_set_semantics() {
3223        let Some(provider) = provider() else { return };
3224        let unit_schema = Schema::new(Vec::new());
3225        let left = provider
3226            .create_zero_arity_buffer(unit_schema.clone(), 1)
3227            .expect("left unit");
3228        let right = provider
3229            .create_zero_arity_buffer(unit_schema.clone(), 1)
3230            .expect("right unit");
3231        let output = provider
3232            .prepare_resident_relation(unit_schema, 1)
3233            .expect("unit output");
3234        let workspace = provider
3235            .prepare_resident_set_workspace(2)
3236            .expect("workspace");
3237        let control = provider
3238            .prepare_resident_convergence_control()
3239            .expect("control");
3240        let stream = provider
3241            .device()
3242            .inner()
3243            .stream()
3244            .context()
3245            .new_stream()
3246            .expect("non-default stream");
3247        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
3248            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
3249            provider.record_resident_union_on_stream(
3250                &left, &right, &output, &workspace, &control, 11, &stream,
3251            )
3252        })
3253        .expect("capture nullary union");
3254        graph.launch(&stream).expect("launch nullary union");
3255        stream.synchronize().expect("nullary union sync");
3256        assert_eq!(
3257            provider
3258                .device()
3259                .inner()
3260                .dtoh_sync_copy(output.num_rows_device())
3261                .expect("unit count"),
3262            vec![1]
3263        );
3264    }
3265
3266    #[test]
3267    fn real_cuda_inner_and_semi_join_preserve_required_multiplicity() {
3268        let Some(provider) = provider() else { return };
3269        let left = pair_buffer(&provider, &[1, 1, 2], &[10, 11, 12]);
3270        let right = pair_buffer(&provider, &[1, 1, 3], &[20, 21, 22]);
3271        let mut inner_columns = pair_schema().columns;
3272        inner_columns.extend(pair_schema().columns);
3273        let inner = provider
3274            .prepare_resident_relation(Schema::new(inner_columns), 4)
3275            .expect("inner output");
3276        let semi = provider
3277            .prepare_resident_relation(pair_schema(), 2)
3278            .expect("semi output");
3279        let inner_workspace = provider
3280            .prepare_resident_join_workspace(3)
3281            .expect("inner workspace");
3282        let semi_workspace = provider
3283            .prepare_resident_join_workspace(3)
3284            .expect("semi workspace");
3285        let control = provider
3286            .prepare_resident_convergence_control()
3287            .expect("control");
3288        let stream = provider
3289            .device()
3290            .inner()
3291            .stream()
3292            .context()
3293            .new_stream()
3294            .expect("non-default stream");
3295        let graph = CapturedCudaGraph::capture_on_stream(&stream, || {
3296            provider.record_resident_control_initialize_on_stream(&control, &stream)?;
3297            provider.record_resident_join_on_stream(
3298                ResidentJoinKind::Inner,
3299                &left,
3300                0,
3301                &right,
3302                0,
3303                &inner,
3304                &inner_workspace,
3305                &control,
3306                8,
3307                &stream,
3308            )?;
3309            provider.record_resident_join_on_stream(
3310                ResidentJoinKind::Semi,
3311                &left,
3312                0,
3313                &right,
3314                0,
3315                &semi,
3316                &semi_workspace,
3317                &control,
3318                9,
3319                &stream,
3320            )
3321        })
3322        .expect("capture resident joins");
3323        graph.launch(&stream).expect("launch resident joins");
3324        stream.synchronize().expect("resident joins sync");
3325        let inner_left_values = provider
3326            .download_column::<u64>(inner.buffer(), 1)
3327            .expect("inner left values");
3328        let inner_right_values = provider
3329            .download_column::<u64>(inner.buffer(), 3)
3330            .expect("inner right values");
3331        let mut pairs: Vec<_> = inner_left_values
3332            .into_iter()
3333            .zip(inner_right_values)
3334            .collect();
3335        pairs.sort_unstable();
3336        assert_eq!(pairs, vec![(10, 20), (10, 21), (11, 20), (11, 21)]);
3337        let mut semi_values = provider
3338            .download_column::<u64>(semi.buffer(), 1)
3339            .expect("semi values");
3340        semi_values.sort_unstable();
3341        assert_eq!(semi_values, vec![10, 11]);
3342    }
3343
3344    #[test]
3345    fn real_cuda_capacity_failure_reports_exact_required_and_clamps_output() {
3346        let Some(provider) = provider() else { return };
3347        let input = pair_buffer(&provider, &[1, 2, 3], &[10, 20, 30]);
3348        let output = provider
3349            .prepare_resident_relation(pair_schema(), 1)
3350            .expect("output");
3351        let workspace = provider
3352            .prepare_resident_set_workspace(3)
3353            .expect("workspace");
3354        let control = provider
3355            .prepare_resident_convergence_control()
3356            .expect("control");
3357        let stream = provider.device().inner().stream();
3358        provider
3359            .record_resident_control_initialize_on_stream(&control, stream)
3360            .expect("init");
3361        provider
3362            .record_resident_dedup_on_stream(&input, &output, &workspace, &control, 23, stream)
3363            .expect("dedup");
3364        stream.synchronize().expect("capacity sync");
3365        let receipt = provider
3366            .device()
3367            .inner()
3368            .dtoh_sync_copy(control.status_device())
3369            .expect("receipt")[0];
3370        assert_eq!(receipt.code, ResidentTerminalCode::CapacityOverflow as u32);
3371        assert_eq!(receipt.op_id, 23);
3372        assert_eq!(
3373            receipt.resource_code,
3374            ResidentResourceCode::OutputRows as u32
3375        );
3376        assert_eq!(receipt.required, 3);
3377        assert_eq!(receipt.capacity, 1);
3378        assert_eq!(
3379            provider
3380                .device()
3381                .inner()
3382                .dtoh_sync_copy(output.num_rows_device())
3383                .unwrap(),
3384            vec![1]
3385        );
3386    }
3387
3388    #[test]
3389    fn real_cuda_set_overflow_reports_exact_required_without_writing_output_storage() {
3390        let Some(provider) = provider() else { return };
3391        let left = pair_buffer(&provider, &[1, 2], &[10, 20]);
3392        let right = pair_buffer(&provider, &[3, 4], &[30, 40]);
3393        let sentinel_keys = [0xdead_beef_u32, 0xcafe_babe];
3394        let sentinel_values = [0x1111_2222_3333_4444_u64, 0x5555_6666_7777_8888];
3395        let output = super::ResidentRelation {
3396            buffer: pair_buffer(&provider, &sentinel_keys, &sentinel_values),
3397        };
3398        let workspace = provider
3399            .prepare_resident_set_workspace(4)
3400            .expect("set workspace");
3401        let control = provider
3402            .prepare_resident_convergence_control()
3403            .expect("set control");
3404        let stream = provider.device().inner().stream();
3405        provider
3406            .record_resident_control_initialize_on_stream(&control, stream)
3407            .expect("set control initialize");
3408        provider
3409            .record_resident_union_on_stream(
3410                &left, &right, &output, &workspace, &control, 31, stream,
3411            )
3412            .expect("overflowing union");
3413        stream.synchronize().expect("overflowing union sync");
3414        let status = provider
3415            .device()
3416            .inner()
3417            .dtoh_sync_copy(control.status_device())
3418            .expect("set overflow status")[0];
3419        assert_eq!(status.code, ResidentTerminalCode::CapacityOverflow as u32);
3420        assert_eq!(status.op_id, 31);
3421        assert_eq!(status.required, 4);
3422        assert_eq!(status.capacity, 2);
3423        let key_bytes: Vec<u8> = sentinel_keys
3424            .iter()
3425            .flat_map(|value| value.to_le_bytes())
3426            .collect();
3427        let value_bytes: Vec<u8> = sentinel_values
3428            .iter()
3429            .flat_map(|value| value.to_le_bytes())
3430            .collect();
3431        assert_eq!(
3432            provider
3433                .device()
3434                .inner()
3435                .dtoh_sync_copy(output.buffer().column(0).expect("set key column"))
3436                .expect("set key storage"),
3437            key_bytes
3438        );
3439        assert_eq!(
3440            provider
3441                .device()
3442                .inner()
3443                .dtoh_sync_copy(output.buffer().column(1).expect("set value column"))
3444                .expect("set value storage"),
3445            value_bytes
3446        );
3447    }
3448
3449    #[test]
3450    fn real_cuda_join_overflow_reports_exact_required_without_writing_output_storage() {
3451        let Some(provider) = provider() else { return };
3452        let left = pair_buffer(&provider, &[1, 1], &[10, 11]);
3453        let right = pair_buffer(&provider, &[1, 1], &[20, 21]);
3454        let mut output_columns = pair_schema().columns;
3455        output_columns.extend(pair_schema().columns);
3456        let output_schema = Schema::new(output_columns);
3457        let sentinel_columns = [
3458            vec![0xaaaa_aaaa_u32.to_le_bytes(), 0xbbbb_bbbb_u32.to_le_bytes()].concat(),
3459            vec![
3460                0x1111_2222_3333_4444_u64.to_le_bytes(),
3461                0x5555_6666_7777_8888_u64.to_le_bytes(),
3462            ]
3463            .concat(),
3464            vec![0xcccc_cccc_u32.to_le_bytes(), 0xdddd_dddd_u32.to_le_bytes()].concat(),
3465            vec![
3466                0x9999_aaaa_bbbb_cccc_u64.to_le_bytes(),
3467                0xdddd_eeee_ffff_0000_u64.to_le_bytes(),
3468            ]
3469            .concat(),
3470        ];
3471        let slices: Vec<&[u8]> = sentinel_columns.iter().map(Vec::as_slice).collect();
3472        let output = super::ResidentRelation {
3473            buffer: provider
3474                .create_buffer_from_slices(&slices, output_schema)
3475                .expect("sentinel join output"),
3476        };
3477        let workspace = provider
3478            .prepare_resident_join_workspace(2)
3479            .expect("join workspace");
3480        let control = provider
3481            .prepare_resident_convergence_control()
3482            .expect("join control");
3483        let stream = provider.device().inner().stream();
3484        provider
3485            .record_resident_control_initialize_on_stream(&control, stream)
3486            .expect("join control initialize");
3487        provider
3488            .record_resident_join_on_stream(
3489                ResidentJoinKind::Inner,
3490                &left,
3491                0,
3492                &right,
3493                0,
3494                &output,
3495                &workspace,
3496                &control,
3497                32,
3498                stream,
3499            )
3500            .expect("overflowing join");
3501        stream.synchronize().expect("overflowing join sync");
3502        let status = provider
3503            .device()
3504            .inner()
3505            .dtoh_sync_copy(control.status_device())
3506            .expect("join overflow status")[0];
3507        assert_eq!(status.code, ResidentTerminalCode::CapacityOverflow as u32);
3508        assert_eq!(status.op_id, 32);
3509        assert_eq!(status.required, 4);
3510        assert_eq!(status.capacity, 2);
3511        for (column, expected) in sentinel_columns.iter().enumerate() {
3512            let actual = provider
3513                .device()
3514                .inner()
3515                .dtoh_sync_copy(output.buffer().column(column).expect("join output column"))
3516                .expect("join output storage");
3517            assert_eq!(actual.as_slice(), expected.as_slice());
3518        }
3519    }
3520
3521    #[test]
3522    fn real_cuda_conditional_while_stops_from_device_convergence() {
3523        let Some(provider) = provider() else { return };
3524        let empty = provider
3525            .create_empty_buffer(Schema::new(vec![("novel".into(), ScalarType::U32)]))
3526            .expect("empty count owner");
3527        let control = provider
3528            .prepare_resident_convergence_control()
3529            .expect("control");
3530        let stream = provider
3531            .device()
3532            .inner()
3533            .stream()
3534            .context()
3535            .new_stream()
3536            .expect("non-default stream");
3537        provider
3538            .record_resident_control_initialize_on_stream(&control, &stream)
3539            .expect("control init");
3540        provider
3541            .record_resident_scc_begin_on_stream(8, 31, &control, &stream)
3542            .expect("SCC begin");
3543        stream.synchronize().expect("cold control init sync");
3544        let graph = CapturedCudaGraph::conditional_while_on_stream(&stream, 1, true, |body| {
3545            body.capture_on_stream(&stream, || {
3546                provider.record_resident_changed_reset_on_stream(&control, &stream)?;
3547                provider.record_resident_changed_mark_on_stream(
3548                    empty.num_rows_device(),
3549                    &control,
3550                    &stream,
3551                )?;
3552                provider.record_resident_convergence_on_stream(
3553                    body.handle(),
3554                    8,
3555                    31,
3556                    &control,
3557                    &stream,
3558                )
3559            })
3560        })
3561        .expect("conditional convergence graph");
3562        graph.launch(&stream).expect("launch conditional graph");
3563        provider
3564            .record_resident_terminal_success_on_stream(99, &control, &stream)
3565            .expect("terminal success");
3566        stream.synchronize().expect("conditional convergence sync");
3567        let receipt = provider
3568            .device()
3569            .inner()
3570            .dtoh_sync_copy(control.status_device())
3571            .expect("convergence receipt")[0];
3572        assert_eq!(receipt.code, ResidentTerminalCode::Success as u32);
3573        assert_eq!(receipt.op_id, 99);
3574        assert_eq!(receipt.iterations, 1);
3575        assert_eq!(
3576            provider
3577                .device()
3578                .inner()
3579                .dtoh_sync_copy(control.changed_device())
3580                .expect("changed flag"),
3581            vec![0]
3582        );
3583    }
3584
3585    #[test]
3586    fn real_cuda_multi_head_convergence_ors_every_recursive_head() {
3587        let Some(provider) = provider() else { return };
3588        let empty = provider
3589            .create_empty_buffer(Schema::new(vec![("empty".into(), ScalarType::U32)]))
3590            .expect("empty head");
3591        let changed = provider
3592            .create_buffer_from_slice(
3593                &[7_u32],
3594                Schema::new(vec![("changed".into(), ScalarType::U32)]),
3595            )
3596            .expect("changed head");
3597        let control = provider
3598            .prepare_resident_convergence_control()
3599            .expect("control");
3600        let stream = provider
3601            .device()
3602            .inner()
3603            .stream()
3604            .context()
3605            .new_stream()
3606            .expect("non-default stream");
3607        provider
3608            .record_resident_control_initialize_on_stream(&control, &stream)
3609            .expect("control init");
3610        provider
3611            .record_resident_scc_begin_on_stream(2, 41, &control, &stream)
3612            .expect("SCC begin");
3613        stream.synchronize().expect("control init sync");
3614        let graph = CapturedCudaGraph::conditional_while_on_stream(&stream, 1, true, |body| {
3615            body.capture_on_stream(&stream, || {
3616                provider.record_resident_changed_reset_on_stream(&control, &stream)?;
3617                provider.record_resident_changed_mark_on_stream(
3618                    empty.num_rows_device(),
3619                    &control,
3620                    &stream,
3621                )?;
3622                provider.record_resident_changed_mark_on_stream(
3623                    changed.num_rows_device(),
3624                    &control,
3625                    &stream,
3626                )?;
3627                provider.record_resident_convergence_on_stream(
3628                    body.handle(),
3629                    2,
3630                    41,
3631                    &control,
3632                    &stream,
3633                )
3634            })
3635        })
3636        .expect("multi-head conditional graph");
3637        graph.launch(&stream).expect("multi-head launch");
3638        stream.synchronize().expect("multi-head sync");
3639        let receipt = provider
3640            .device()
3641            .inner()
3642            .dtoh_sync_copy(control.status_device())
3643            .expect("multi-head receipt")[0];
3644        assert_eq!(receipt.code, ResidentTerminalCode::IterationLimit as u32);
3645        assert_eq!(receipt.op_id, 41);
3646        assert_eq!(receipt.iterations, 2);
3647        assert_eq!(receipt.limit, 2);
3648    }
3649
3650    #[test]
3651    fn real_cuda_zero_iteration_limit_fails_before_body_replay() {
3652        let Some(provider) = provider() else { return };
3653        let control = provider
3654            .prepare_resident_convergence_control()
3655            .expect("control");
3656        let stream = provider.device().inner().stream();
3657        provider
3658            .record_resident_control_initialize_on_stream(&control, stream)
3659            .expect("control init");
3660        provider
3661            .record_resident_scc_begin_on_stream(0, 51, &control, stream)
3662            .expect("zero-limit SCC begin");
3663        stream.synchronize().expect("zero-limit sync");
3664        let receipt = provider
3665            .device()
3666            .inner()
3667            .dtoh_sync_copy(control.status_device())
3668            .expect("zero-limit receipt")[0];
3669        assert_eq!(receipt.code, ResidentTerminalCode::IterationLimit as u32);
3670        assert_eq!(receipt.op_id, 51);
3671        assert_eq!(receipt.iterations, 0);
3672        assert_eq!(receipt.limit, 0);
3673    }
3674}