1use 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
23pub const RESIDENT_RELATIONAL_MAX_ARITY: usize = 17;
25
26#[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#[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#[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
67unsafe 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
88pub struct ResidentRelation {
90 buffer: CudaBuffer,
91}
92
93impl ResidentRelation {
94 pub fn buffer(&self) -> &CudaBuffer {
95 &self.buffer
96 }
97
98 pub fn into_buffer(self) -> CudaBuffer {
102 self.buffer
103 }
104
105 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 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 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
189pub 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
222pub 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
259pub struct ResidentConvergenceControl {
261 status: TrackedCudaSlice<ResidentTerminalStatus>,
262 changed: TrackedCudaSlice<u32>,
263 loop_iterations: TrackedCudaSlice<u32>,
264}
265
266pub 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
274pub 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 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 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
523pub 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#[derive(Debug)]
536pub struct ResidentPinnedReceipt {
537 ptr: std::ptr::NonNull<u8>,
538 len: usize,
539}
540
541unsafe 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 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#[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
704pub 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
713pub 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
722pub 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
736pub const fn resident_control_device_bytes() -> u64 {
738 (std::mem::size_of::<ResidentTerminalStatus>() + 2 * std::mem::size_of::<u32>()) as u64
739}
740
741pub const fn resident_device_trace_bytes() -> u64 {
743 (4 * std::mem::size_of::<u32>()) as u64
744}
745
746pub 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
779pub fn resident_packed_receipt_device_bytes(head_count: usize) -> Result<u64> {
781 resident_packed_receipt_device_bytes_for_fields(head_count, 0)
782}
783
784pub 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 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 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 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 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 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 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 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 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 pub fn prepare_resident_device_trace(&self) -> Result<ResidentDeviceTrace> {
1098 self.prepare_resident_device_trace_with_reservation(None)
1099 }
1100
1101 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 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 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 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 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 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 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 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 pub fn prepare_resident_pinned_receipt(
1493 &self,
1494 receipt: &ResidentPackedReceipt,
1495 ) -> Result<ResidentPinnedReceipt> {
1496 let mut ptr = std::ptr::null_mut();
1497 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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}