1use std::ptr::NonNull;
2use std::sync::Arc;
3
4use cudarc::driver::{sys, CudaStream, DeviceRepr, LaunchConfig};
5use xlog_core::{Result, ScalarType, XlogError};
6
7use super::resident_filter_project::ResidentFilterScratch;
8use super::resident_relational::{
9 ResidentConvergenceControl, ResidentDeviceTrace, ResidentJoinWorkspace, ResidentPackedReceipt,
10 ResidentSchemaWinners, ResidentSetWorkspace, ResidentTerminalStatus,
11};
12use super::CudaKernelProvider;
13use crate::cuda_compat::{AsKernelParam, DeviceSlice, LaunchAsync};
14use crate::cuda_graph::{
15 CapturedCudaGraph, ConditionalCudaGraphBody, ConditionalCudaGraphSequenceBuilder, CudaGraphNode,
16};
17use crate::device_runtime::{StreamId, XlogDeviceRuntime};
18use crate::launch::LaunchRecorder;
19use crate::memory::{
20 CudaBuffer, GpuMemoryReservation, RuntimeAllocationIdentity, TrackedCudaSlice,
21};
22
23const RESIDENT_SCHEDULE_MAX_ARITY: usize = 17;
24const RESIDENT_SCHEDULE_BLOCK_SIZE: u32 = 256;
25pub const RESIDENT_SCHEDULE_ABI_VERSION: u32 = 3;
26const RESIDENT_SCHEDULE_MAX_ROWS: u64 = 65_536;
27const MODULE: &str = "xlog_resident_schedule";
28const KERNEL: &str = "resident_schedule_execute";
29pub const RESIDENT_SCHEDULE_SLOT_SOURCE: u32 = 1;
30pub const RESIDENT_SCHEDULE_SLOT_PERMANENT: u32 = 2;
31pub const RESIDENT_SCHEDULE_SLOT_DEFINED: u32 = 4;
32const SOURCE_SLOT: u32 = RESIDENT_SCHEDULE_SLOT_SOURCE;
33
34fn validate_runtime_allocation_fields(
35 manager_id: usize,
36 allocation_ptr: u64,
37 allocation_bytes: usize,
38 block_id: crate::device_runtime::BlockId,
39 block_bytes: usize,
40 block_state: crate::device_runtime::BlockState,
41 expected_manager_id: usize,
42 expected_device_ordinal: u32,
43) -> Result<(u64, u64)> {
44 if manager_id != expected_manager_id {
45 return Err(XlogError::Kernel(
46 "resident schedule allocation belongs to a foreign memory manager".into(),
47 ));
48 }
49 if block_state != crate::device_runtime::BlockState::Live {
50 return Err(XlogError::Kernel(
51 "resident schedule allocation runtime block is not live".into(),
52 ));
53 }
54 if block_id.device_ordinal != expected_device_ordinal {
55 return Err(XlogError::Kernel(
56 "resident schedule allocation belongs to a foreign CUDA device".into(),
57 ));
58 }
59 let allocation_bytes = u64::try_from(allocation_bytes)
60 .map_err(|_| XlogError::Kernel("resident schedule allocation size overflow".into()))?;
61 let block_bytes = u64::try_from(block_bytes)
62 .map_err(|_| XlogError::Kernel("resident schedule runtime block size overflow".into()))?;
63 let allocation_end = allocation_ptr
64 .checked_add(allocation_bytes)
65 .ok_or_else(|| XlogError::Kernel("resident schedule allocation range overflow".into()))?;
66 let block_end = block_id.ptr.checked_add(block_bytes).ok_or_else(|| {
67 XlogError::Kernel("resident schedule runtime block range overflow".into())
68 })?;
69 if allocation_ptr < block_id.ptr || allocation_end > block_end {
70 return Err(XlogError::Kernel(
71 "resident schedule allocation range is outside its runtime block".into(),
72 ));
73 }
74 Ok((allocation_ptr, allocation_end))
75}
76
77fn validate_runtime_allocation_identity(
78 identity: &RuntimeAllocationIdentity,
79 domain: &ResidentExecutionDomain,
80) -> Result<(u64, u64)> {
81 let range = validate_runtime_allocation_fields(
82 identity.manager_id,
83 identity.allocation_ptr,
84 identity.allocation_bytes,
85 identity.block_id,
86 identity.block_bytes,
87 identity.block_state,
88 domain.memory_manager_identity,
89 domain.runtime.device_ordinal(),
90 )?;
91 if !Arc::ptr_eq(&identity.context, &domain.context)
92 || identity.context.cu_ctx() != domain.context.cu_ctx()
93 {
94 return Err(XlogError::Kernel(
95 "resident schedule allocation belongs to a foreign CUDA context".into(),
96 ));
97 }
98 Ok(range)
99}
100
101fn validate_schedule_allocation(
102 identity: Option<RuntimeAllocationIdentity>,
103 domain: &ResidentExecutionDomain,
104 ranges: &mut Vec<(u64, u64)>,
105) -> Result<RuntimeAllocationIdentity> {
106 let identity = identity.ok_or_else(|| {
107 XlogError::Kernel(
108 "resident schedule requires every allocation to be runtime tracked".into(),
109 )
110 })?;
111 let range = validate_runtime_allocation_identity(&identity, domain)?;
112 insert_nonoverlapping_allocation_range(ranges, range)?;
113 Ok(identity)
114}
115
116fn insert_nonoverlapping_allocation_range(
117 ranges: &mut Vec<(u64, u64)>,
118 range: (u64, u64),
119) -> Result<()> {
120 if range.0 >= range.1 {
121 return Err(XlogError::Kernel(
122 "resident schedule allocation range is empty or reversed".into(),
123 ));
124 }
125 if ranges
126 .iter()
127 .any(|previous| range.0 < previous.1 && previous.0 < range.1)
128 {
129 return Err(XlogError::Kernel(
130 "resident schedule allocations have overlapping byte ranges".into(),
131 ));
132 }
133 ranges.push(range);
134 Ok(())
135}
136
137fn validate_receipt_slot_mapping(
138 receipt_slots: &[u32],
139 slot_flags: &[u32],
140 head_count: u32,
141) -> Result<Vec<usize>> {
142 let head_count = usize::try_from(head_count)
143 .map_err(|_| XlogError::Kernel("resident schedule head count overflow".into()))?;
144 if receipt_slots.len() != head_count {
145 return Err(XlogError::Kernel(
146 "resident schedule receipt slot count does not match its head count".into(),
147 ));
148 }
149 let mut validated = Vec::with_capacity(receipt_slots.len());
150 for &slot in receipt_slots {
151 let slot = usize::try_from(slot)
152 .map_err(|_| XlogError::Kernel("resident schedule receipt slot overflow".into()))?;
153 let flags = *slot_flags.get(slot).ok_or_else(|| {
154 XlogError::Kernel("resident schedule receipt slot is out of range".into())
155 })?;
156 if flags & RESIDENT_SCHEDULE_SLOT_SOURCE != 0
157 || flags & RESIDENT_SCHEDULE_SLOT_PERMANENT == 0
158 {
159 return Err(XlogError::Kernel(
160 "resident schedule receipt slot is not a permanent output".into(),
161 ));
162 }
163 if validated.contains(&slot) {
164 return Err(XlogError::Kernel(
165 "resident schedule receipt slots contain a duplicate".into(),
166 ));
167 }
168 validated.push(slot);
169 }
170 Ok(validated)
171}
172
173fn validate_execution_domain(
174 provider: &CudaKernelProvider,
175 domain: &ResidentExecutionDomain,
176) -> Result<()> {
177 let manager_id = Arc::as_ptr(provider.memory()) as usize;
178 let manager_runtime = provider.memory().runtime().ok_or_else(|| {
179 XlogError::Kernel(
180 "resident execution domain requires a runtime-backed memory manager".into(),
181 )
182 })?;
183 if domain.provider_identity != provider.provider_identity()
184 || domain.memory_manager_identity != manager_id
185 || !Arc::ptr_eq(manager_runtime, &domain.runtime)
186 || !Arc::ptr_eq(provider.device(), provider.memory().device())
187 || !Arc::ptr_eq(provider.device(), domain.runtime.device())
188 {
189 return Err(XlogError::Kernel(
190 "resident execution domain provider, manager, and runtime identities differ".into(),
191 ));
192 }
193 let device_ordinal = u32::try_from(provider.device().ordinal()).map_err(|_| {
194 XlogError::Kernel("resident execution domain device ordinal overflow".into())
195 })?;
196 if domain.runtime.device_ordinal() != device_ordinal
197 || !domain.runtime.supports_block_use_tracking()
198 {
199 return Err(XlogError::Kernel(
200 "resident execution domain runtime is incompatible with the provider".into(),
201 ));
202 }
203 let resolved_stream = domain
204 .runtime
205 .stream_pool()
206 .resolve(domain.stream_id)
207 .ok_or_else(|| {
208 XlogError::Kernel(
209 "resident execution domain stream id is not owned by the runtime".into(),
210 )
211 })?;
212 if !Arc::ptr_eq(&resolved_stream, &domain.stream) {
213 return Err(XlogError::Kernel(
214 "resident execution domain stream does not match its runtime stream id".into(),
215 ));
216 }
217 let provider_context = provider.device().inner().stream().context();
218 if !Arc::ptr_eq(&domain.context, provider_context)
219 || !Arc::ptr_eq(domain.stream.context(), &domain.context)
220 || domain.context.cu_ctx() != provider_context.cu_ctx()
221 || domain.stream.context().cu_ctx() != domain.context.cu_ctx()
222 {
223 return Err(XlogError::Kernel(
224 "resident execution domain belongs to a foreign CUDA context".into(),
225 ));
226 }
227 Ok(())
228}
229
230#[repr(u32)]
232#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
233pub enum ResidentScheduleOpKind {
234 #[default]
235 Unit = 0,
236 Scan = 1,
237 Filter = 2,
238 Project = 3,
239 JoinInner = 4,
240 JoinSemi = 5,
241 Union = 6,
242 Diff = 7,
243 TestStatus = 8,
244 TraceDelta = 9,
245}
246
247const OP_UNIT: ResidentScheduleOpKind = ResidentScheduleOpKind::Unit;
248const OP_SCAN: ResidentScheduleOpKind = ResidentScheduleOpKind::Scan;
249const OP_FILTER: ResidentScheduleOpKind = ResidentScheduleOpKind::Filter;
250const OP_PROJECT: ResidentScheduleOpKind = ResidentScheduleOpKind::Project;
251const OP_JOIN_INNER: ResidentScheduleOpKind = ResidentScheduleOpKind::JoinInner;
252const OP_JOIN_SEMI: ResidentScheduleOpKind = ResidentScheduleOpKind::JoinSemi;
253const OP_UNION: ResidentScheduleOpKind = ResidentScheduleOpKind::Union;
254const OP_DIFF: ResidentScheduleOpKind = ResidentScheduleOpKind::Diff;
255const OP_TEST_STATUS: ResidentScheduleOpKind = ResidentScheduleOpKind::TestStatus;
256const OP_TRACE_DELTA: ResidentScheduleOpKind = ResidentScheduleOpKind::TraceDelta;
257
258pub const RESIDENT_SCHEDULE_OP_MARK_NOVELTY: u32 = 1;
259pub const RESIDENT_SCHEDULE_OP_MARK_SCHEMA_WINNER: u32 = 2;
260pub const RESIDENT_SCHEDULE_TRACE_SEMANTIC_GUARD: u32 = 1;
261pub const RESIDENT_SCHEDULE_REGION_INITIALIZE: u32 = 1;
262pub const RESIDENT_SCHEDULE_REGION_SCC_BEGIN: u32 = 2;
263pub const RESIDENT_SCHEDULE_REGION_RECURSIVE: u32 = 4;
264pub const RESIDENT_SCHEDULE_REGION_FINALIZE: u32 = 8;
265
266#[repr(C, align(8))]
268#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
269pub struct ResidentRelationView {
270 pub columns: [u64; RESIDENT_SCHEDULE_MAX_ARITY],
271 pub widths: [u32; RESIDENT_SCHEDULE_MAX_ARITY],
272 pub arity: u32,
273 pub capacity: u32,
274 pub reserved: u32,
275 pub num_rows: u64,
276}
277
278unsafe impl DeviceRepr for ResidentRelationView {}
280
281#[repr(C, align(16))]
283#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
284pub struct ResidentRelationSlot {
285 pub relation: ResidentRelationView,
286 pub generation: u32,
287 pub flags: u32,
288 pub initial_count: u32,
289 pub schema_tag: u32,
290}
291
292unsafe impl DeviceRepr for ResidentRelationSlot {}
294
295#[repr(C)]
297#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
298pub struct ResidentOpDescriptor {
299 pub kind: ResidentScheduleOpKind,
300 pub flags: u32,
301 pub op_id: u32,
302 pub out: u32,
303 pub in0: u32,
304 pub in1: u32,
305 pub in0_generation: u32,
306 pub in1_generation: u32,
307 pub out_generation: u32,
308 pub aux_offset: u32,
309 pub aux_count: u32,
310 pub left_key: u32,
311 pub right_key: u32,
312 pub scan_delta: u32,
313 pub filter_delta: u32,
314 pub schema_winner_head: u32,
315 pub schema_winner_id: u32,
316 pub reserved: u32,
317}
318
319unsafe impl DeviceRepr for ResidentOpDescriptor {}
322
323impl ResidentOpDescriptor {
324 pub fn unit(op_id: u32, out: u32, out_generation: u32) -> Self {
326 Self {
327 kind: ResidentScheduleOpKind::Unit,
328 op_id,
329 out,
330 out_generation,
331 ..Self::default()
332 }
333 }
334
335 pub fn scan(op_id: u32, source: u32, source_generation: u32) -> Self {
337 Self {
338 kind: ResidentScheduleOpKind::Scan,
339 op_id,
340 out: source,
341 in0: source,
342 in0_generation: source_generation,
343 out_generation: source_generation,
344 ..Self::default()
345 }
346 }
347
348 pub fn with_schema_winner(mut self, head: u32, schema_id: u32) -> Self {
349 self.flags |= RESIDENT_SCHEDULE_OP_MARK_SCHEMA_WINNER;
350 self.schema_winner_head = head;
351 self.schema_winner_id = schema_id;
352 self
353 }
354
355 pub fn test_status(status: ResidentTerminalStatus) -> Result<Self> {
356 if status.reserved != 0 {
357 return Err(XlogError::Kernel(
358 "resident schedule test status reserved field is nonzero".into(),
359 ));
360 }
361 Ok(Self {
362 kind: OP_TEST_STATUS,
363 op_id: status.op_id,
364 out: status.code,
365 in0: status.resource_code,
366 in1: status.iterations,
367 in0_generation: status.limit,
368 in1_generation: status.reserved,
369 out_generation: status.required as u32,
370 aux_offset: (status.required >> 32) as u32,
371 aux_count: status.capacity as u32,
372 left_key: (status.capacity >> 32) as u32,
373 ..Default::default()
374 })
375 }
376
377 pub fn trace_delta(
378 scan_delta: u32,
379 filter_delta: u32,
380 semantic_guard: Option<(u32, u32)>,
381 ) -> Self {
382 let (flags, in0, in0_generation) = match semantic_guard {
383 Some((slot, generation)) => (RESIDENT_SCHEDULE_TRACE_SEMANTIC_GUARD, slot, generation),
384 None => (0, 0, 0),
385 };
386 Self {
387 kind: OP_TRACE_DELTA,
388 flags,
389 in0,
390 in0_generation,
391 scan_delta,
392 filter_delta,
393 ..Default::default()
394 }
395 }
396}
397
398fn decode_test_status(op: &ResidentOpDescriptor) -> Result<ResidentTerminalStatus> {
399 if op.kind != OP_TEST_STATUS
400 || op.flags != 0
401 || op.in1_generation != 0
402 || op.right_key != 0
403 || op.scan_delta != 0
404 || op.filter_delta != 0
405 || op.schema_winner_head != 0
406 || op.schema_winner_id != 0
407 || op.reserved != 0
408 {
409 return Err(XlogError::Kernel(
410 "resident schedule test status descriptor is invalid".into(),
411 ));
412 }
413 Ok(ResidentTerminalStatus {
414 code: op.out,
415 op_id: op.op_id,
416 resource_code: op.in0,
417 iterations: op.in1,
418 limit: op.in0_generation,
419 reserved: op.in1_generation,
420 required: u64::from(op.out_generation) | (u64::from(op.aux_offset) << 32),
421 capacity: u64::from(op.aux_count) | (u64::from(op.left_key) << 32),
422 })
423}
424
425fn decode_trace_delta(op: &ResidentOpDescriptor) -> Result<(u32, u32, Option<(u32, u32)>)> {
426 let has_semantic_guard = op.flags & RESIDENT_SCHEDULE_TRACE_SEMANTIC_GUARD != 0;
427 if op.kind != OP_TRACE_DELTA
428 || op.flags & !RESIDENT_SCHEDULE_TRACE_SEMANTIC_GUARD != 0
429 || op.op_id != 0
430 || op.out != 0
431 || op.in1 != 0
432 || op.in1_generation != 0
433 || op.out_generation != 0
434 || op.aux_offset != 0
435 || op.aux_count != 0
436 || op.left_key != 0
437 || op.right_key != 0
438 || op.schema_winner_head != 0
439 || op.schema_winner_id != 0
440 || op.reserved != 0
441 || (!has_semantic_guard && (op.in0 != 0 || op.in0_generation != 0))
442 {
443 return Err(XlogError::Kernel(
444 "resident schedule trace delta descriptor is invalid".into(),
445 ));
446 }
447 Ok((
448 op.scan_delta,
449 op.filter_delta,
450 has_semantic_guard.then_some((op.in0, op.in0_generation)),
451 ))
452}
453
454#[repr(C)]
456#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
457pub struct ResidentWaveDescriptor {
458 pub first_op: u32,
459 pub op_count: u32,
460 pub flags: u32,
461 pub reserved: u32,
462}
463
464unsafe impl DeviceRepr for ResidentWaveDescriptor {}
466
467#[repr(C)]
469#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
470pub struct ResidentRegionDescriptor {
471 pub first_wave: u32,
472 pub wave_count: u32,
473 pub iteration_limit: u32,
474 pub op_id: u32,
475 pub flags: u32,
476 pub first_slot: u32,
477 pub slot_count: u32,
478 pub generation_offset: u32,
479}
480
481unsafe impl DeviceRepr for ResidentRegionDescriptor {}
483
484#[repr(C, align(16))]
486#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
487pub struct ResidentScheduleHeader {
488 pub slots: u64,
489 pub ops: u64,
490 pub waves: u64,
491 pub regions: u64,
492 pub generation_metadata: u64,
493 pub filter_comparisons: u64,
494 pub project_expressions: u64,
495 pub filter_mask: u64,
496 pub filter_prefix: u64,
497 pub filter_block_sums: u64,
498 pub filter_block_offsets: u64,
499 pub set_slots: u64,
500 pub set_required: u64,
501 pub join_buckets: u64,
502 pub join_next: u64,
503 pub join_required: u64,
504 pub status: u64,
505 pub changed: u64,
506 pub iterations: u64,
507 pub scan_trace: u64,
508 pub filter_trace: u64,
509 pub semantic_scan_trace: u64,
510 pub semantic_filter_trace: u64,
511 pub schema_seen_nonempty: u64,
512 pub schema_winner_ids: u64,
513 pub receipt_table: u64,
514 pub receipt_bytes: u64,
515 pub slot_count: u32,
516 pub op_count: u32,
517 pub wave_count: u32,
518 pub region_count: u32,
519 pub filter_comparison_count: u32,
520 pub project_expression_count: u32,
521 pub filter_capacity: u32,
522 pub filter_block_count: u32,
523 pub set_slot_mask: u32,
524 pub set_candidate_capacity: u32,
525 pub join_bucket_mask: u32,
526 pub join_right_capacity: u32,
527 pub schema_winner_count: u32,
528 pub receipt_count: u32,
529 pub receipt_byte_count: u32,
530 pub generation_metadata_count: u32,
531 pub abi_version: u32,
532 pub reserved: u32,
533}
534
535unsafe impl DeviceRepr for ResidentScheduleHeader {}
537
538#[repr(C, align(8))]
540#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
541pub struct ResidentFilterComparisonDescriptor {
542 pub left_kind: u32,
543 pub left_column: u32,
544 pub right_kind: u32,
545 pub right_column: u32,
546 pub op: u32,
547 pub width: u32,
548 pub reserved_zero: u32,
549 pub reserved_one: u32,
550 pub left_constant: u64,
551 pub right_constant: u64,
552}
553
554unsafe impl DeviceRepr for ResidentFilterComparisonDescriptor {}
556
557impl ResidentFilterComparisonDescriptor {
558 pub fn column_constant(column: u32, op: u32, width: u32, constant: u64) -> Self {
559 Self {
560 left_kind: 0,
561 left_column: column,
562 right_kind: 1,
563 right_column: 0,
564 op,
565 width,
566 reserved_zero: 0,
567 reserved_one: 0,
568 left_constant: 0,
569 right_constant: constant,
570 }
571 }
572}
573
574#[repr(C, align(8))]
576#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
577pub struct ResidentProjectExpressionDescriptor {
578 pub kind: u32,
579 pub column: u32,
580 pub width: u32,
581 pub reserved: u32,
582 pub constant: u64,
583}
584
585unsafe impl DeviceRepr for ResidentProjectExpressionDescriptor {}
587
588impl ResidentProjectExpressionDescriptor {
589 pub fn column(column: u32, width: u32) -> Self {
590 Self {
591 kind: 0,
592 column,
593 width,
594 reserved: 0,
595 constant: 0,
596 }
597 }
598
599 pub fn constant(width: u32, constant: u64) -> Self {
600 Self {
601 kind: 1,
602 column: 0,
603 width,
604 reserved: 0,
605 constant,
606 }
607 }
608}
609
610pub enum ResidentScheduleRelation<'a> {
612 Source {
613 buffer: &'a CudaBuffer,
614 generation: u32,
615 initial_count: u32,
616 },
617 Output {
618 buffer: &'a mut CudaBuffer,
619 generation: u32,
620 },
621}
622
623pub enum ResidentScheduleSlotBinding<'a> {
625 Source {
626 buffer: &'a CudaBuffer,
627 generation: u32,
628 initial_count: u32,
629 },
630 Resident {
631 buffer: &'a CudaBuffer,
632 generation: u32,
633 permanent: bool,
634 },
635}
636
637impl<'a> ResidentScheduleSlotBinding<'a> {
638 pub fn source(buffer: &'a CudaBuffer, generation: u32) -> Result<Self> {
639 let initial_count = buffer.cached_row_count().ok_or_else(|| {
640 XlogError::Kernel(
641 "resident schedule source requires a cold-path cached logical row count".into(),
642 )
643 })?;
644 if u64::from(initial_count) > buffer.num_rows() {
645 return Err(XlogError::Kernel(
646 "resident schedule source count exceeds capacity".into(),
647 ));
648 }
649 Ok(Self::Source {
650 buffer,
651 generation,
652 initial_count,
653 })
654 }
655
656 pub fn scratch(buffer: &'a CudaBuffer, generation: u32) -> Self {
657 Self::Resident {
658 buffer,
659 generation,
660 permanent: false,
661 }
662 }
663
664 pub fn permanent(buffer: &'a CudaBuffer, generation: u32) -> Self {
665 Self::Resident {
666 buffer,
667 generation,
668 permanent: true,
669 }
670 }
671
672 fn buffer(&self) -> &CudaBuffer {
673 match self {
674 Self::Source { buffer, .. } | Self::Resident { buffer, .. } => buffer,
675 }
676 }
677
678 fn generation(&self) -> u32 {
679 match self {
680 Self::Source { generation, .. } | Self::Resident { generation, .. } => *generation,
681 }
682 }
683
684 fn flags(&self) -> u32 {
685 match self {
686 Self::Source { .. } => RESIDENT_SCHEDULE_SLOT_SOURCE | RESIDENT_SCHEDULE_SLOT_DEFINED,
687 Self::Resident {
688 permanent: true, ..
689 } => RESIDENT_SCHEDULE_SLOT_PERMANENT | RESIDENT_SCHEDULE_SLOT_DEFINED,
690 Self::Resident {
691 permanent: false, ..
692 } => 0,
693 }
694 }
695
696 fn initial_count(&self) -> u32 {
697 match self {
698 Self::Source { initial_count, .. } => *initial_count,
699 Self::Resident { .. } => 0,
700 }
701 }
702
703 pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
705 match self {
706 Self::Source { buffer, .. } => {
707 for column in buffer.columns() {
708 recorder.read_column(column);
709 }
710 recorder.read(buffer.num_rows_device());
711 }
712 Self::Resident { buffer, .. } => {
713 for column in buffer.columns() {
714 recorder.read_column(column);
715 recorder.write_column(column);
716 }
717 recorder.read_write(buffer.num_rows_device());
718 }
719 }
720 }
721}
722
723pub struct ResidentScheduleExternalBindings<'a> {
725 filter_scratch: Option<&'a ResidentFilterScratch>,
726 set_workspace: &'a ResidentSetWorkspace,
727 join_workspace: &'a ResidentJoinWorkspace,
728 control: &'a ResidentConvergenceControl,
729 trace: &'a ResidentDeviceTrace,
730 schema_winners: &'a ResidentSchemaWinners,
731 receipt: &'a ResidentPackedReceipt,
732}
733
734impl<'a> ResidentScheduleExternalBindings<'a> {
735 #[allow(clippy::too_many_arguments)]
736 pub fn new(
737 filter_scratch: Option<&'a ResidentFilterScratch>,
738 set_workspace: &'a ResidentSetWorkspace,
739 join_workspace: &'a ResidentJoinWorkspace,
740 control: &'a ResidentConvergenceControl,
741 trace: &'a ResidentDeviceTrace,
742 schema_winners: &'a ResidentSchemaWinners,
743 receipt: &'a ResidentPackedReceipt,
744 ) -> Self {
745 Self {
746 filter_scratch,
747 set_workspace,
748 join_workspace,
749 control,
750 trace,
751 schema_winners,
752 receipt,
753 }
754 }
755
756 pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
758 if let Some(filter_scratch) = self.filter_scratch {
759 filter_scratch.record_uses(recorder);
760 }
761 self.set_workspace.record_uses(recorder);
762 self.join_workspace.record_uses(recorder);
763 self.control.record_uses(recorder);
764 self.trace.record_uses(recorder);
765 self.schema_winners.record_uses(recorder);
766 self.receipt.record_uses(recorder);
767 }
768}
769
770impl<'a> ResidentScheduleRelation<'a> {
771 pub fn source(buffer: &'a CudaBuffer, generation: u32) -> Result<Self> {
772 let initial_count = buffer.cached_row_count().ok_or_else(|| {
773 XlogError::Kernel(
774 "resident schedule source requires a cold-path cached logical row count".into(),
775 )
776 })?;
777 if u64::from(initial_count) > buffer.num_rows() {
778 return Err(XlogError::Kernel(format!(
779 "resident schedule source count {initial_count} exceeds capacity {}",
780 buffer.num_rows()
781 )));
782 }
783 Ok(Self::Source {
784 buffer,
785 generation,
786 initial_count,
787 })
788 }
789
790 pub fn output(buffer: &'a mut CudaBuffer, generation: u32) -> Self {
791 Self::Output { buffer, generation }
792 }
793
794 fn buffer(&self) -> &CudaBuffer {
795 match self {
796 Self::Source { buffer, .. } => buffer,
797 Self::Output { buffer, .. } => buffer,
798 }
799 }
800
801 fn generation(&self) -> u32 {
802 match self {
803 Self::Source { generation, .. } | Self::Output { generation, .. } => *generation,
804 }
805 }
806
807 fn flags(&self) -> u32 {
808 match self {
809 Self::Source { .. } => SOURCE_SLOT,
810 Self::Output { .. } => 0,
811 }
812 }
813
814 fn initial_count(&self) -> u32 {
815 match self {
816 Self::Source { initial_count, .. } => *initial_count,
817 Self::Output { .. } => 0,
818 }
819 }
820
821 fn invalidate_output_metadata(&mut self) {
822 if let Self::Output { buffer, .. } = self {
823 let _ = buffer.num_rows_device_mut();
824 }
825 }
826
827 fn is_output(&self) -> bool {
828 matches!(self, Self::Output { .. })
829 }
830}
831
832#[derive(Debug, Clone, PartialEq, Eq)]
834pub struct ResidentScheduleReceipt {
835 pub status: ResidentTerminalStatus,
836 pub changed: u32,
837 pub counts: Vec<u32>,
838}
839
840struct ResidentSchedulePinnedReceipt {
841 ptr: NonNull<u8>,
842 len: usize,
843}
844
845impl ResidentSchedulePinnedReceipt {
846 fn allocate(len: usize) -> Result<Self> {
847 let mut ptr = std::ptr::null_mut();
848 let code = unsafe { sys::cuMemHostAlloc(&mut ptr, len, 0) };
850 if code != sys::cudaError_enum::CUDA_SUCCESS {
851 return Err(XlogError::Kernel(format!(
852 "resident schedule pinned receipt allocation failed: {code:?}"
853 )));
854 }
855 let ptr = NonNull::new(ptr.cast()).ok_or_else(|| {
856 XlogError::Kernel("resident schedule pinned receipt allocation returned null".into())
857 })?;
858 Ok(Self { ptr, len })
859 }
860
861 fn copy_from_device(&mut self, device_ptr: u64, stream: &CudaStream) -> Result<Vec<u8>> {
862 let code = unsafe {
865 sys::cuMemcpyDtoHAsync_v2(
866 self.ptr.as_ptr().cast(),
867 device_ptr,
868 self.len,
869 stream.cu_stream(),
870 )
871 };
872 if code != sys::cudaError_enum::CUDA_SUCCESS {
873 return Err(XlogError::Kernel(format!(
874 "resident schedule final receipt copy failed: {code:?}"
875 )));
876 }
877 stream.synchronize().map_err(|error| {
878 XlogError::Kernel(format!(
879 "resident schedule final receipt wait failed: {error}"
880 ))
881 })?;
882 Ok(unsafe { std::slice::from_raw_parts(self.ptr.as_ptr(), self.len) }.to_vec())
884 }
885}
886
887impl Drop for ResidentSchedulePinnedReceipt {
888 fn drop(&mut self) {
889 let _ = unsafe { sys::cuMemFreeHost(self.ptr.as_ptr().cast()) };
891 }
892}
893
894pub struct ResidentSchedule<'a> {
896 origin_provider_identity: u64,
897 origin_memory_manager: usize,
898 header: TrackedCudaSlice<ResidentScheduleHeader>,
899 _slots: TrackedCudaSlice<ResidentRelationSlot>,
900 _ops: TrackedCudaSlice<ResidentOpDescriptor>,
901 _waves: TrackedCudaSlice<ResidentWaveDescriptor>,
902 _regions: TrackedCudaSlice<ResidentRegionDescriptor>,
903 _generation_metadata: TrackedCudaSlice<u32>,
904 _filter_comparisons: TrackedCudaSlice<ResidentFilterComparisonDescriptor>,
905 _project_expressions: TrackedCudaSlice<ResidentProjectExpressionDescriptor>,
906 _filter_mask: TrackedCudaSlice<u32>,
907 _filter_prefix: TrackedCudaSlice<u32>,
908 _filter_block_sums: TrackedCudaSlice<u32>,
909 _filter_block_offsets: TrackedCudaSlice<u32>,
910 _set_slots: TrackedCudaSlice<u64>,
911 _set_required: TrackedCudaSlice<u64>,
912 _join_buckets: TrackedCudaSlice<u32>,
913 _join_next: TrackedCudaSlice<u32>,
914 _join_required: TrackedCudaSlice<u64>,
915 _status: TrackedCudaSlice<ResidentTerminalStatus>,
916 _changed: TrackedCudaSlice<u32>,
917 _iterations: TrackedCudaSlice<u32>,
918 _scan_trace: TrackedCudaSlice<u32>,
919 _filter_trace: TrackedCudaSlice<u32>,
920 _semantic_scan_trace: TrackedCudaSlice<u32>,
921 _semantic_filter_trace: TrackedCudaSlice<u32>,
922 _receipt_table: TrackedCudaSlice<u64>,
923 receipt_bytes: TrackedCudaSlice<u8>,
924 pinned_receipt: ResidentSchedulePinnedReceipt,
925 launch_config: LaunchConfig,
926 region_count: u32,
927 region_descriptors: Vec<ResidentRegionDescriptor>,
928 requested_receipt_count: usize,
929 receipt_slots: Vec<u32>,
930 relations: Vec<ResidentScheduleRelation<'a>>,
931}
932
933#[derive(Clone)]
935pub struct ResidentExecutionDomain {
936 provider_identity: u64,
937 memory_manager_identity: usize,
938 runtime: Arc<XlogDeviceRuntime>,
939 stream_id: StreamId,
940 stream: Arc<CudaStream>,
941 context: Arc<cudarc::driver::CudaContext>,
942 marker: Arc<()>,
943}
944
945impl ResidentExecutionDomain {
946 pub fn new_strict_recorder(&self) -> LaunchRecorder {
947 LaunchRecorder::new_strict_bound(
948 self.stream_id,
949 Arc::clone(&self.runtime),
950 Arc::clone(&self.marker),
951 )
952 }
953
954 pub fn preflight(&self, recorder: &mut LaunchRecorder) -> Result<()> {
955 recorder.require_bound_domain(&self.runtime, &self.marker, self.stream_id);
956 recorder
957 .preflight_bound(&self.runtime)
958 .map_err(|error| XlogError::Kernel(format!("resident launch preflight: {error}")))
959 }
960
961 pub fn commit(&self, recorder: LaunchRecorder) -> Result<()> {
962 recorder
963 .commit_bound(&self.runtime, &self.marker)
964 .map_err(|error| XlogError::Kernel(format!("resident launch commit: {error}")))
965 }
966
967 pub fn stream(&self) -> &Arc<CudaStream> {
968 &self.stream
969 }
970
971 pub fn stream_id(&self) -> StreamId {
972 self.stream_id
973 }
974}
975
976pub struct ResidentScheduleDeviceProgram {
978 origin_provider_identity: u64,
979 domain: ResidentExecutionDomain,
980 header: TrackedCudaSlice<ResidentScheduleHeader>,
981 _slots: TrackedCudaSlice<ResidentRelationSlot>,
982 _ops: TrackedCudaSlice<ResidentOpDescriptor>,
983 _waves: TrackedCudaSlice<ResidentWaveDescriptor>,
984 _regions: TrackedCudaSlice<ResidentRegionDescriptor>,
985 _generation_metadata: TrackedCudaSlice<u32>,
986 _filter_comparisons: TrackedCudaSlice<ResidentFilterComparisonDescriptor>,
987 _project_expressions: TrackedCudaSlice<ResidentProjectExpressionDescriptor>,
988 launch_config: LaunchConfig,
989 region_descriptors: Vec<ResidentRegionDescriptor>,
990}
991
992impl ResidentScheduleDeviceProgram {
993 pub fn record_uses(&self, recorder: &mut LaunchRecorder) {
995 recorder.require_bound_domain(
996 &self.domain.runtime,
997 &self.domain.marker,
998 self.domain.stream_id,
999 );
1000 recorder.read(&self.header);
1001 recorder.read_write(&self._slots);
1002 recorder.read(&self._ops);
1003 recorder.read(&self._waves);
1004 recorder.read(&self._regions);
1005 recorder.read(&self._generation_metadata);
1006 recorder.read(&self._filter_comparisons);
1007 recorder.read(&self._project_expressions);
1008 }
1009}
1010
1011pub struct ResidentScheduleGraph<'a> {
1013 graph: CapturedCudaGraph,
1014 schedule: ResidentSchedule<'a>,
1015 provider: &'a CudaKernelProvider,
1016 stream: Arc<CudaStream>,
1017 in_flight: bool,
1018}
1019
1020impl ResidentScheduleGraph<'_> {
1021 pub fn node_count(&self) -> Result<usize> {
1022 self.graph.node_count()
1023 }
1024
1025 pub fn nodes(&self) -> Result<Vec<CudaGraphNode>> {
1026 self.graph.nodes()
1027 }
1028
1029 pub fn launch(&mut self) -> Result<()> {
1030 if self.in_flight {
1031 return Err(XlogError::Kernel(
1032 "resident schedule launch is already in flight".into(),
1033 ));
1034 }
1035 self.graph.launch(&self.stream)?;
1036 self.in_flight = true;
1037 Ok(())
1038 }
1039
1040 pub fn synchronize_and_observe(&mut self) -> Result<ResidentScheduleReceipt> {
1041 if !self.in_flight {
1042 return Err(XlogError::Kernel(
1043 "resident schedule has no in-flight launch to observe".into(),
1044 ));
1045 }
1046 self.stream
1047 .synchronize()
1048 .map_err(|error| XlogError::Kernel(format!("resident schedule sync: {error}")))?;
1049 let receipt = self
1050 .provider
1051 .observe_resident_schedule(&mut self.schedule, &self.stream)?;
1052 self.in_flight = false;
1053 Ok(receipt)
1054 }
1055
1056 pub fn relation(&self, slot: usize) -> Result<&CudaBuffer> {
1057 if self.in_flight {
1058 return Err(XlogError::Kernel(
1059 "resident schedule relation is unavailable while launch is in flight".into(),
1060 ));
1061 }
1062 self.schedule
1063 .relations
1064 .get(slot)
1065 .map(ResidentScheduleRelation::buffer)
1066 .ok_or_else(|| XlogError::Kernel(format!("resident schedule slot {slot} is invalid")))
1067 }
1068}
1069
1070impl Drop for ResidentScheduleGraph<'_> {
1071 fn drop(&mut self) {
1072 if self.in_flight {
1073 let _ = self.stream.synchronize();
1074 }
1075 }
1076}
1077
1078const _: () = {
1079 use std::mem::{align_of, offset_of, size_of};
1080
1081 assert!(size_of::<ResidentRelationView>() == 224);
1082 assert!(align_of::<ResidentRelationView>() == 8);
1083 assert!(offset_of!(ResidentRelationView, columns) == 0);
1084 assert!(offset_of!(ResidentRelationView, widths) == 136);
1085 assert!(offset_of!(ResidentRelationView, arity) == 204);
1086 assert!(offset_of!(ResidentRelationView, capacity) == 208);
1087 assert!(offset_of!(ResidentRelationView, reserved) == 212);
1088 assert!(offset_of!(ResidentRelationView, num_rows) == 216);
1089
1090 assert!(size_of::<ResidentRelationSlot>() == 240);
1091 assert!(align_of::<ResidentRelationSlot>() == 16);
1092 assert!(offset_of!(ResidentRelationSlot, relation) == 0);
1093 assert!(offset_of!(ResidentRelationSlot, generation) == 224);
1094 assert!(offset_of!(ResidentRelationSlot, flags) == 228);
1095 assert!(offset_of!(ResidentRelationSlot, initial_count) == 232);
1096 assert!(offset_of!(ResidentRelationSlot, schema_tag) == 236);
1097
1098 assert!(size_of::<ResidentOpDescriptor>() == 72);
1099 assert!(align_of::<ResidentOpDescriptor>() == 4);
1100 assert!(offset_of!(ResidentOpDescriptor, kind) == 0);
1101 assert!(offset_of!(ResidentOpDescriptor, flags) == 4);
1102 assert!(offset_of!(ResidentOpDescriptor, op_id) == 8);
1103 assert!(offset_of!(ResidentOpDescriptor, out) == 12);
1104 assert!(offset_of!(ResidentOpDescriptor, in0) == 16);
1105 assert!(offset_of!(ResidentOpDescriptor, in1) == 20);
1106 assert!(offset_of!(ResidentOpDescriptor, in0_generation) == 24);
1107 assert!(offset_of!(ResidentOpDescriptor, in1_generation) == 28);
1108 assert!(offset_of!(ResidentOpDescriptor, out_generation) == 32);
1109 assert!(offset_of!(ResidentOpDescriptor, aux_offset) == 36);
1110 assert!(offset_of!(ResidentOpDescriptor, aux_count) == 40);
1111 assert!(offset_of!(ResidentOpDescriptor, left_key) == 44);
1112 assert!(offset_of!(ResidentOpDescriptor, right_key) == 48);
1113 assert!(offset_of!(ResidentOpDescriptor, scan_delta) == 52);
1114 assert!(offset_of!(ResidentOpDescriptor, filter_delta) == 56);
1115 assert!(offset_of!(ResidentOpDescriptor, schema_winner_head) == 60);
1116 assert!(offset_of!(ResidentOpDescriptor, schema_winner_id) == 64);
1117 assert!(offset_of!(ResidentOpDescriptor, reserved) == 68);
1118
1119 assert!(size_of::<ResidentWaveDescriptor>() == 16);
1120 assert!(align_of::<ResidentWaveDescriptor>() == 4);
1121 assert!(offset_of!(ResidentWaveDescriptor, first_op) == 0);
1122 assert!(offset_of!(ResidentWaveDescriptor, op_count) == 4);
1123 assert!(offset_of!(ResidentWaveDescriptor, flags) == 8);
1124 assert!(offset_of!(ResidentWaveDescriptor, reserved) == 12);
1125
1126 assert!(size_of::<ResidentRegionDescriptor>() == 32);
1127 assert!(align_of::<ResidentRegionDescriptor>() == 4);
1128 assert!(offset_of!(ResidentRegionDescriptor, first_wave) == 0);
1129 assert!(offset_of!(ResidentRegionDescriptor, wave_count) == 4);
1130 assert!(offset_of!(ResidentRegionDescriptor, iteration_limit) == 8);
1131 assert!(offset_of!(ResidentRegionDescriptor, op_id) == 12);
1132 assert!(offset_of!(ResidentRegionDescriptor, flags) == 16);
1133 assert!(offset_of!(ResidentRegionDescriptor, first_slot) == 20);
1134 assert!(offset_of!(ResidentRegionDescriptor, slot_count) == 24);
1135 assert!(offset_of!(ResidentRegionDescriptor, generation_offset) == 28);
1136
1137 assert!(size_of::<ResidentScheduleHeader>() == 288);
1138 assert!(align_of::<ResidentScheduleHeader>() == 16);
1139 assert!(offset_of!(ResidentScheduleHeader, slots) == 0);
1140 assert!(offset_of!(ResidentScheduleHeader, ops) == 8);
1141 assert!(offset_of!(ResidentScheduleHeader, waves) == 16);
1142 assert!(offset_of!(ResidentScheduleHeader, regions) == 24);
1143 assert!(offset_of!(ResidentScheduleHeader, generation_metadata) == 32);
1144 assert!(offset_of!(ResidentScheduleHeader, filter_comparisons) == 40);
1145 assert!(offset_of!(ResidentScheduleHeader, project_expressions) == 48);
1146 assert!(offset_of!(ResidentScheduleHeader, filter_mask) == 56);
1147 assert!(offset_of!(ResidentScheduleHeader, filter_prefix) == 64);
1148 assert!(offset_of!(ResidentScheduleHeader, filter_block_sums) == 72);
1149 assert!(offset_of!(ResidentScheduleHeader, filter_block_offsets) == 80);
1150 assert!(offset_of!(ResidentScheduleHeader, set_slots) == 88);
1151 assert!(offset_of!(ResidentScheduleHeader, set_required) == 96);
1152 assert!(offset_of!(ResidentScheduleHeader, join_buckets) == 104);
1153 assert!(offset_of!(ResidentScheduleHeader, join_next) == 112);
1154 assert!(offset_of!(ResidentScheduleHeader, join_required) == 120);
1155 assert!(offset_of!(ResidentScheduleHeader, status) == 128);
1156 assert!(offset_of!(ResidentScheduleHeader, changed) == 136);
1157 assert!(offset_of!(ResidentScheduleHeader, iterations) == 144);
1158 assert!(offset_of!(ResidentScheduleHeader, scan_trace) == 152);
1159 assert!(offset_of!(ResidentScheduleHeader, filter_trace) == 160);
1160 assert!(offset_of!(ResidentScheduleHeader, semantic_scan_trace) == 168);
1161 assert!(offset_of!(ResidentScheduleHeader, semantic_filter_trace) == 176);
1162 assert!(offset_of!(ResidentScheduleHeader, schema_seen_nonempty) == 184);
1163 assert!(offset_of!(ResidentScheduleHeader, schema_winner_ids) == 192);
1164 assert!(offset_of!(ResidentScheduleHeader, receipt_table) == 200);
1165 assert!(offset_of!(ResidentScheduleHeader, receipt_bytes) == 208);
1166 assert!(offset_of!(ResidentScheduleHeader, slot_count) == 216);
1167 assert!(offset_of!(ResidentScheduleHeader, schema_winner_count) == 264);
1168 assert!(offset_of!(ResidentScheduleHeader, generation_metadata_count) == 276);
1169 assert!(offset_of!(ResidentScheduleHeader, abi_version) == 280);
1170 assert!(offset_of!(ResidentScheduleHeader, reserved) == 284);
1171
1172 assert!(size_of::<ResidentFilterComparisonDescriptor>() == 48);
1173 assert!(align_of::<ResidentFilterComparisonDescriptor>() == 8);
1174 assert!(offset_of!(ResidentFilterComparisonDescriptor, left_constant) == 32);
1175 assert!(offset_of!(ResidentFilterComparisonDescriptor, right_constant) == 40);
1176
1177 assert!(size_of::<ResidentProjectExpressionDescriptor>() == 24);
1178 assert!(align_of::<ResidentProjectExpressionDescriptor>() == 8);
1179 assert!(offset_of!(ResidentProjectExpressionDescriptor, constant) == 16);
1180};
1181
1182pub fn resident_schedule_metadata_device_bytes(
1183 slot_count: usize,
1184 op_count: usize,
1185 wave_count: usize,
1186 region_count: usize,
1187 generation_metadata_count: usize,
1188 filter_comparison_count: usize,
1189 project_expression_count: usize,
1190) -> Result<u64> {
1191 let tables = [
1192 (slot_count.max(1), size_of::<ResidentRelationSlot>()),
1193 (op_count.max(1), size_of::<ResidentOpDescriptor>()),
1194 (wave_count.max(1), size_of::<ResidentWaveDescriptor>()),
1195 (region_count.max(1), size_of::<ResidentRegionDescriptor>()),
1196 (generation_metadata_count.max(1), size_of::<u32>()),
1197 (
1198 filter_comparison_count.max(1),
1199 size_of::<ResidentFilterComparisonDescriptor>(),
1200 ),
1201 (
1202 project_expression_count.max(1),
1203 size_of::<ResidentProjectExpressionDescriptor>(),
1204 ),
1205 ];
1206 let total = tables.iter().try_fold(
1207 size_of::<ResidentScheduleHeader>() as u128,
1208 |sum, &(count, width)| sum.checked_add((count as u128) * (width as u128)),
1209 );
1210 total
1211 .and_then(|bytes| u64::try_from(bytes).ok())
1212 .ok_or_else(|| XlogError::Kernel("resident schedule metadata bytes overflow".into()))
1213}
1214
1215fn checked_u32(value: usize, context: &str) -> Result<u32> {
1216 u32::try_from(value)
1217 .map_err(|_| XlogError::Kernel(format!("resident schedule {context} exceeds u32")))
1218}
1219
1220fn checked_capacity(value: u64, context: &str) -> Result<u32> {
1221 if value > RESIDENT_SCHEDULE_MAX_ROWS {
1222 return Err(XlogError::Kernel(format!(
1223 "resident schedule {context} capacity {value} exceeds {RESIDENT_SCHEDULE_MAX_ROWS}"
1224 )));
1225 }
1226 u32::try_from(value)
1227 .map_err(|_| XlogError::Kernel(format!("resident schedule {context} exceeds u32")))
1228}
1229
1230fn checked_workspace_slots(candidate_capacity: u64, context: &str) -> Result<u32> {
1231 let doubled = candidate_capacity
1232 .max(1)
1233 .checked_mul(2)
1234 .ok_or_else(|| XlogError::Kernel(format!("resident schedule {context} overflow")))?;
1235 let slots = doubled
1236 .checked_next_power_of_two()
1237 .ok_or_else(|| XlogError::Kernel(format!("resident schedule {context} overflow")))?;
1238 u32::try_from(slots)
1239 .map_err(|_| XlogError::Kernel(format!("resident schedule {context} exceeds u32")))
1240}
1241
1242fn reset_slot_flags(flags: u32) -> u32 {
1243 if flags & (RESIDENT_SCHEDULE_SLOT_SOURCE | RESIDENT_SCHEDULE_SLOT_PERMANENT) != 0 {
1244 flags | RESIDENT_SCHEDULE_SLOT_DEFINED
1245 } else {
1246 flags & !RESIDENT_SCHEDULE_SLOT_DEFINED
1247 }
1248}
1249
1250fn reset_slot_state_for_region(flags: u32, generation: u32, count: u32) -> (u32, u32, u32) {
1251 let fixed = flags & (RESIDENT_SCHEDULE_SLOT_SOURCE | RESIDENT_SCHEDULE_SLOT_PERMANENT) != 0;
1252 (
1253 reset_slot_flags(flags),
1254 generation,
1255 if fixed { count } else { 0 },
1256 )
1257}
1258
1259fn slot_input_is_ready(flags: u32, generation: u32, expected_generation: u32) -> bool {
1260 flags & RESIDENT_SCHEDULE_SLOT_DEFINED != 0 && generation == expected_generation
1261}
1262
1263fn slot_output_generation_is_valid(flags: u32, generation: u32, output_generation: u32) -> bool {
1264 flags & RESIDENT_SCHEDULE_SLOT_SOURCE == 0
1265 && (output_generation == generation || generation.checked_add(1) == Some(output_generation))
1266}
1267
1268fn finish_slot_write(flags: u32, success: bool) -> u32 {
1269 if success {
1270 flags | RESIDENT_SCHEDULE_SLOT_DEFINED
1271 } else {
1272 flags
1273 }
1274}
1275
1276fn checked_schedule_head_count(receipt_count: u32, receipt_byte_count: u32) -> Result<u32> {
1277 let remainder = receipt_count.checked_sub(4).filter(|value| value % 2 == 0);
1278 let expected_bytes = receipt_count
1279 .checked_add(1)
1280 .and_then(|count| count.checked_mul(size_of::<u32>() as u32))
1281 .and_then(|count_bytes| {
1282 (size_of::<ResidentTerminalStatus>() as u32).checked_add(count_bytes)
1283 });
1284 if remainder.is_none() || expected_bytes != Some(receipt_byte_count) {
1285 return Err(XlogError::Kernel(
1286 "resident schedule packed receipt shape is invalid".into(),
1287 ));
1288 }
1289 Ok(remainder.expect("validated receipt remainder") / 2)
1290}
1291
1292fn checked_schedule_winner_count(
1293 receipt_count: u32,
1294 receipt_byte_count: u32,
1295 schema_winner_count: u32,
1296) -> Result<u32> {
1297 let head_count = checked_schedule_head_count(receipt_count, receipt_byte_count)?;
1298 if schema_winner_count != head_count {
1299 return Err(XlogError::Kernel(
1300 "resident schedule schema winner count does not match its receipt".into(),
1301 ));
1302 }
1303 Ok(head_count)
1304}
1305
1306fn validate_schema_winner_encoding(op: &ResidentOpDescriptor, head_count: u32) -> Result<()> {
1307 let marks_winner = op.flags & RESIDENT_SCHEDULE_OP_MARK_SCHEMA_WINNER != 0;
1308 if (marks_winner && op.schema_winner_head >= head_count)
1309 || (!marks_winner && (op.schema_winner_head != 0 || op.schema_winner_id != 0))
1310 {
1311 return Err(XlogError::Kernel(format!(
1312 "resident schedule operation {} has an invalid schema winner encoding",
1313 op.op_id
1314 )));
1315 }
1316 Ok(())
1317}
1318
1319fn same_relation_layout(left: &ResidentRelationView, right: &ResidentRelationView) -> bool {
1320 left.arity == right.arity
1321 && left.widths[..left.arity as usize] == right.widths[..right.arity as usize]
1322}
1323
1324fn column_width_matches(relation: &ResidentRelationView, column: u32, width: u32) -> bool {
1325 let column = column as usize;
1326 column < relation.arity as usize && relation.widths[column] == width
1327}
1328
1329fn validate_flattened_filter_project_descriptors(
1330 slots: &[ResidentRelationSlot],
1331 slot_types: &[Vec<ScalarType>],
1332 ops: &[ResidentOpDescriptor],
1333 filter_comparisons: &[ResidentFilterComparisonDescriptor],
1334 project_expressions: &[ResidentProjectExpressionDescriptor],
1335) -> Result<()> {
1336 if slots.len() != slot_types.len() {
1337 return Err(XlogError::Kernel(
1338 "resident schedule slot type table length is invalid".into(),
1339 ));
1340 }
1341 for (slot, types) in slots.iter().zip(slot_types) {
1342 if types.len() != slot.relation.arity as usize {
1343 return Err(XlogError::Kernel(
1344 "resident schedule slot type table arity is invalid".into(),
1345 ));
1346 }
1347 for (column, scalar) in types.iter().copied().enumerate() {
1348 if resident_schedule_scalar_width(scalar)? != slot.relation.widths[column] {
1349 return Err(XlogError::Kernel(
1350 "resident schedule slot type width is invalid".into(),
1351 ));
1352 }
1353 }
1354 }
1355
1356 let operand_type = |relation: &ResidentRelationView,
1357 types: &[ScalarType],
1358 kind: u32,
1359 column: u32,
1360 width: u32,
1361 constant: u64|
1362 -> Result<Option<ScalarType>> {
1363 if !matches!(width, 4 | 8) {
1364 return Err(XlogError::Kernel(
1365 "resident schedule descriptor scalar width is invalid".into(),
1366 ));
1367 }
1368 match kind {
1369 0 => {
1370 if constant != 0 || !column_width_matches(relation, column, width) {
1371 return Err(XlogError::Kernel(
1372 "resident schedule descriptor column is invalid".into(),
1373 ));
1374 }
1375 Ok(types.get(column as usize).copied())
1376 }
1377 1 if column == 0 => Ok(None),
1378 _ => Err(XlogError::Kernel(
1379 "resident schedule descriptor operand kind is invalid".into(),
1380 )),
1381 }
1382 };
1383
1384 let mut filter_cursor = 0_u32;
1385 let mut project_cursor = 0_u32;
1386 let filter_total = checked_u32(filter_comparisons.len(), "filter comparison count")?;
1387 let project_total = checked_u32(project_expressions.len(), "project expression count")?;
1388 for op in ops {
1389 if op.kind == OP_FILTER {
1390 if op.in0 as usize >= slots.len()
1391 || op.out as usize >= slots.len()
1392 || op.aux_offset != filter_cursor
1393 || op.aux_offset > filter_total
1394 || op.aux_count > filter_total - op.aux_offset
1395 {
1396 return Err(XlogError::Kernel(format!(
1397 "resident schedule filter {} descriptor range is invalid",
1398 op.op_id
1399 )));
1400 }
1401 let input = &slots[op.in0 as usize].relation;
1402 let output = &slots[op.out as usize].relation;
1403 if slots[op.in0 as usize].schema_tag != slots[op.out as usize].schema_tag
1404 || !same_relation_layout(input, output)
1405 {
1406 return Err(XlogError::Kernel(format!(
1407 "resident schedule filter {} input and output schemas differ",
1408 op.op_id
1409 )));
1410 }
1411 let types = &slot_types[op.in0 as usize];
1412 for comparison in
1413 &filter_comparisons[op.aux_offset as usize..(op.aux_offset + op.aux_count) as usize]
1414 {
1415 if comparison.op > 5
1416 || comparison.reserved_zero != 0
1417 || comparison.reserved_one != 0
1418 {
1419 return Err(XlogError::Kernel(format!(
1420 "resident schedule filter {} descriptor payload is invalid",
1421 op.op_id
1422 )));
1423 }
1424 let left = operand_type(
1425 input,
1426 types,
1427 comparison.left_kind,
1428 comparison.left_column,
1429 comparison.width,
1430 comparison.left_constant,
1431 )?;
1432 let right = operand_type(
1433 input,
1434 types,
1435 comparison.right_kind,
1436 comparison.right_column,
1437 comparison.width,
1438 comparison.right_constant,
1439 )?;
1440 if left.is_some() && right.is_some() && left != right {
1441 return Err(XlogError::Kernel(format!(
1442 "resident schedule filter {} operand types differ",
1443 op.op_id
1444 )));
1445 }
1446 }
1447 filter_cursor = op.aux_offset + op.aux_count;
1448 } else if op.kind == OP_PROJECT {
1449 if op.in0 as usize >= slots.len()
1450 || op.out as usize >= slots.len()
1451 || op.aux_offset != project_cursor
1452 || op.aux_offset > project_total
1453 || op.aux_count > project_total - op.aux_offset
1454 {
1455 return Err(XlogError::Kernel(format!(
1456 "resident schedule project {} descriptor range is invalid",
1457 op.op_id
1458 )));
1459 }
1460 let input = &slots[op.in0 as usize].relation;
1461 let output = &slots[op.out as usize].relation;
1462 if op.aux_count != output.arity {
1463 return Err(XlogError::Kernel(format!(
1464 "resident schedule project {} expression count is invalid",
1465 op.op_id
1466 )));
1467 }
1468 let input_types = &slot_types[op.in0 as usize];
1469 let output_types = &slot_types[op.out as usize];
1470 for (column, expression) in project_expressions
1471 [op.aux_offset as usize..(op.aux_offset + op.aux_count) as usize]
1472 .iter()
1473 .enumerate()
1474 {
1475 if expression.reserved != 0
1476 || expression.width != output.widths[column]
1477 || !matches!(expression.width, 4 | 8)
1478 {
1479 return Err(XlogError::Kernel(format!(
1480 "resident schedule project {} descriptor payload is invalid",
1481 op.op_id
1482 )));
1483 }
1484 match expression.kind {
1485 0 => {
1486 if expression.constant != 0
1487 || !column_width_matches(input, expression.column, expression.width)
1488 || input_types.get(expression.column as usize)
1489 != output_types.get(column)
1490 {
1491 return Err(XlogError::Kernel(format!(
1492 "resident schedule project {} column type is invalid",
1493 op.op_id
1494 )));
1495 }
1496 }
1497 1 if expression.column == 0 => {}
1498 _ => {
1499 return Err(XlogError::Kernel(format!(
1500 "resident schedule project {} expression kind is invalid",
1501 op.op_id
1502 )));
1503 }
1504 }
1505 }
1506 project_cursor = op.aux_offset + op.aux_count;
1507 }
1508 }
1509 if filter_cursor != filter_total || project_cursor != project_total {
1510 return Err(XlogError::Kernel(
1511 "resident schedule flattened descriptor tables are not exactly covered".into(),
1512 ));
1513 }
1514 Ok(())
1515}
1516
1517fn resident_schedule_scalar_width(scalar: ScalarType) -> Result<u32> {
1518 match scalar {
1519 ScalarType::Symbol | ScalarType::U32 => Ok(4),
1520 ScalarType::U64 => Ok(8),
1521 unsupported => Err(XlogError::Kernel(format!(
1522 "resident schedule scalar type {unsupported:?} is unsupported"
1523 ))),
1524 }
1525}
1526
1527fn resident_schedule_scalar_tag(scalar: ScalarType) -> u32 {
1528 match scalar {
1529 ScalarType::Symbol => 1,
1530 ScalarType::U32 => 2,
1531 ScalarType::U64 => 3,
1532 _ => 0,
1533 }
1534}
1535
1536fn validate_initialization_scope(
1537 initial_region: &ResidentRegionDescriptor,
1538 slot_count: u32,
1539) -> Result<()> {
1540 if initial_region.first_slot != 0 || initial_region.slot_count != slot_count {
1541 return Err(XlogError::Kernel(
1542 "resident schedule initialization must cover every relation slot".into(),
1543 ));
1544 }
1545 Ok(())
1546}
1547
1548fn validate_wave_partition(waves: &[ResidentWaveDescriptor], op_count: u32) -> Result<()> {
1549 let mut next_op = 0_u32;
1550 for wave in waves {
1551 if wave.first_op != next_op
1552 || wave.first_op > op_count
1553 || wave.op_count > op_count - wave.first_op
1554 || wave.flags != 0
1555 || wave.reserved != 0
1556 {
1557 return Err(XlogError::Kernel(
1558 "resident schedule waves must exactly partition operations".into(),
1559 ));
1560 }
1561 next_op = wave.first_op + wave.op_count;
1562 }
1563 if next_op != op_count {
1564 return Err(XlogError::Kernel(
1565 "resident schedule waves must exactly partition operations".into(),
1566 ));
1567 }
1568 Ok(())
1569}
1570
1571fn validate_region_control_and_ranges(
1572 regions: &[ResidentRegionDescriptor],
1573 wave_count: u32,
1574 slot_count: u32,
1575) -> Result<()> {
1576 if regions.is_empty() {
1577 return Err(XlogError::Kernel(
1578 "resident schedule requires at least one region".into(),
1579 ));
1580 }
1581 validate_initialization_scope(®ions[0], slot_count)?;
1582 let allowed_flags = RESIDENT_SCHEDULE_REGION_INITIALIZE
1583 | RESIDENT_SCHEDULE_REGION_SCC_BEGIN
1584 | RESIDENT_SCHEDULE_REGION_RECURSIVE
1585 | RESIDENT_SCHEDULE_REGION_FINALIZE;
1586 let mut next_wave = 0_u32;
1587 for (index, region) in regions.iter().enumerate() {
1588 if region.first_wave > wave_count
1589 || region.wave_count > wave_count - region.first_wave
1590 || region.first_slot > slot_count
1591 || region.slot_count > slot_count - region.first_slot
1592 || region.first_wave != next_wave
1593 || region.flags & !allowed_flags != 0
1594 {
1595 return Err(XlogError::Kernel(
1596 "resident schedule region range or reserved field is invalid".into(),
1597 ));
1598 }
1599 let initializes = region.flags & RESIDENT_SCHEDULE_REGION_INITIALIZE != 0;
1600 let begins_scc = region.flags & RESIDENT_SCHEDULE_REGION_SCC_BEGIN != 0;
1601 let recursive = region.flags & RESIDENT_SCHEDULE_REGION_RECURSIVE != 0;
1602 let finalizes = region.flags & RESIDENT_SCHEDULE_REGION_FINALIZE != 0;
1603 if initializes != (index == 0)
1604 || finalizes != (index + 1 == regions.len())
1605 || (recursive && region.flags != RESIDENT_SCHEDULE_REGION_RECURSIVE)
1606 || (begins_scc && (recursive || finalizes))
1607 || (!begins_scc && !recursive && region.iteration_limit != 1)
1608 {
1609 return Err(XlogError::Kernel(
1610 "resident schedule region control flags are invalid".into(),
1611 ));
1612 }
1613 if begins_scc {
1614 let body = regions.get(index + 1).ok_or_else(|| {
1615 XlogError::Kernel("resident schedule SCC begin has no recursive body".into())
1616 })?;
1617 if body.flags != RESIDENT_SCHEDULE_REGION_RECURSIVE
1618 || body.iteration_limit != region.iteration_limit
1619 || body.op_id != region.op_id
1620 {
1621 return Err(XlogError::Kernel(
1622 "resident schedule SCC begin does not match its recursive body".into(),
1623 ));
1624 }
1625 } else if recursive {
1626 let seed = index
1627 .checked_sub(1)
1628 .and_then(|seed_index| regions.get(seed_index))
1629 .ok_or_else(|| {
1630 XlogError::Kernel("resident schedule recursive body has no SCC begin".into())
1631 })?;
1632 if seed.flags & RESIDENT_SCHEDULE_REGION_SCC_BEGIN == 0 {
1633 return Err(XlogError::Kernel(
1634 "resident schedule recursive body has no SCC begin".into(),
1635 ));
1636 }
1637 }
1638 next_wave = region.first_wave + region.wave_count;
1639 }
1640 if next_wave != wave_count {
1641 return Err(XlogError::Kernel(
1642 "resident schedule regions do not cover every wave".into(),
1643 ));
1644 }
1645 Ok(())
1646}
1647
1648fn validate_generation_baseline_ranges(
1649 regions: &[ResidentRegionDescriptor],
1650 generation_base_count: u32,
1651) -> Result<()> {
1652 let mut cursor = 0_u32;
1653 for region in regions {
1654 if region.generation_offset != cursor {
1655 return Err(XlogError::Kernel(
1656 "resident schedule generation baselines are not contiguous".into(),
1657 ));
1658 }
1659 cursor = cursor.checked_add(region.slot_count).ok_or_else(|| {
1660 XlogError::Kernel("resident schedule generation baseline range overflow".into())
1661 })?;
1662 if cursor > generation_base_count {
1663 return Err(XlogError::Kernel(
1664 "resident schedule generation baseline range is invalid".into(),
1665 ));
1666 }
1667 }
1668 if cursor != generation_base_count {
1669 return Err(XlogError::Kernel(
1670 "resident schedule generation baseline table has trailing entries".into(),
1671 ));
1672 }
1673 Ok(())
1674}
1675
1676fn build_generation_baselines(
1677 regions: &mut [ResidentRegionDescriptor],
1678 slot_generations: &[u32],
1679) -> Result<Vec<u32>> {
1680 let mut baselines = Vec::new();
1681 for region in regions {
1682 if region.generation_offset != 0 {
1683 return Err(XlogError::Kernel(
1684 "resident schedule generation baselines are not contiguous".into(),
1685 ));
1686 }
1687 region.generation_offset = checked_u32(baselines.len(), "generation baseline count")?;
1688 let first = usize::try_from(region.first_slot).unwrap_or(usize::MAX);
1689 let count = usize::try_from(region.slot_count).unwrap_or(usize::MAX);
1690 let end = first
1691 .checked_add(count)
1692 .filter(|&end| end <= slot_generations.len())
1693 .ok_or_else(|| {
1694 XlogError::Kernel(
1695 "resident schedule generation baseline slot scope is invalid".into(),
1696 )
1697 })?;
1698 baselines.extend_from_slice(&slot_generations[first..end]);
1699 }
1700 checked_u32(baselines.len(), "generation baseline count")?;
1701 Ok(baselines)
1702}
1703
1704fn build_generation_metadata(
1705 generation_bases: &[u32],
1706 schema_defaults: &[u32],
1707) -> Result<Vec<u32>> {
1708 let metadata_count = generation_bases
1709 .len()
1710 .checked_add(schema_defaults.len())
1711 .ok_or_else(|| {
1712 XlogError::Kernel("resident schedule generation metadata overflow".into())
1713 })?;
1714 let mut metadata = Vec::with_capacity(metadata_count);
1715 metadata.extend_from_slice(generation_bases);
1716 metadata.extend_from_slice(schema_defaults);
1717 Ok(metadata)
1718}
1719
1720fn generation_baseline_count_from_metadata(
1721 generation_metadata_count: u32,
1722 schema_winner_count: u32,
1723) -> Result<u32> {
1724 generation_metadata_count
1725 .checked_sub(schema_winner_count)
1726 .ok_or_else(|| {
1727 XlogError::Kernel(
1728 "resident schedule generation metadata is shorter than its schema-default tail"
1729 .into(),
1730 )
1731 })
1732}
1733
1734#[cfg(test)]
1735fn reset_schema_winner_state(
1736 defaults: &[u32],
1737 seen_nonempty: &mut [u32],
1738 winner_ids: &mut [u32],
1739) -> Result<()> {
1740 if defaults.len() != seen_nonempty.len() || defaults.len() != winner_ids.len() {
1741 return Err(XlogError::Kernel(
1742 "resident schedule schema-winner replay state has the wrong shape".into(),
1743 ));
1744 }
1745 seen_nonempty.fill(0);
1746 winner_ids.copy_from_slice(defaults);
1747 Ok(())
1748}
1749
1750#[cfg(test)]
1751fn mark_schema_winner_model(
1752 contribution_count: u32,
1753 candidate_id: u32,
1754 seen_nonempty: &mut u32,
1755 winner_id: &mut u32,
1756) {
1757 if contribution_count != 0 && *seen_nonempty == 0 {
1758 *seen_nonempty = 1;
1759 *winner_id = candidate_id;
1760 }
1761}
1762
1763#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1764struct ResidentScheduleRequirements {
1765 filter_capacity: u32,
1766 set_candidate_capacity: u64,
1767 join_right_capacity: u32,
1768}
1769
1770#[allow(clippy::too_many_arguments)]
1771fn validate_schedule_program(
1772 slots: &[ResidentRelationSlot],
1773 slot_types: &[Vec<ScalarType>],
1774 ops: &[ResidentOpDescriptor],
1775 waves: &[ResidentWaveDescriptor],
1776 regions: &[ResidentRegionDescriptor],
1777 generation_bases: &[u32],
1778 filter_comparisons: &[ResidentFilterComparisonDescriptor],
1779 project_expressions: &[ResidentProjectExpressionDescriptor],
1780 schema_defaults: &[u32],
1781) -> Result<ResidentScheduleRequirements> {
1782 validate_flattened_filter_project_descriptors(
1783 slots,
1784 slot_types,
1785 ops,
1786 filter_comparisons,
1787 project_expressions,
1788 )?;
1789 let op_count = checked_u32(ops.len(), "operation count")?;
1790 let wave_count = checked_u32(waves.len(), "wave count")?;
1791 let slot_count = checked_u32(slots.len(), "slot count")?;
1792 let generation_base_count = checked_u32(generation_bases.len(), "generation baseline count")?;
1793 validate_wave_partition(waves, op_count)?;
1794 validate_region_control_and_ranges(regions, wave_count, slot_count)?;
1795 validate_generation_baseline_ranges(regions, generation_base_count)?;
1796 let head_count = checked_u32(schema_defaults.len(), "schema default count")?;
1797 let mut first_schema_candidates = vec![None; schema_defaults.len()];
1798 let mut requirements = ResidentScheduleRequirements::default();
1799 let allowed_slot_flags = RESIDENT_SCHEDULE_SLOT_SOURCE
1800 | RESIDENT_SCHEDULE_SLOT_PERMANENT
1801 | RESIDENT_SCHEDULE_SLOT_DEFINED;
1802 for slot in slots {
1803 if slot.flags & !allowed_slot_flags != 0
1804 || slot.flags & RESIDENT_SCHEDULE_SLOT_SOURCE != 0
1805 && slot.flags & RESIDENT_SCHEDULE_SLOT_PERMANENT != 0
1806 {
1807 return Err(XlogError::Kernel(
1808 "resident schedule relation slot flags are invalid".into(),
1809 ));
1810 }
1811 }
1812
1813 for region in regions {
1814 let recursive = region.flags == RESIDENT_SCHEDULE_REGION_RECURSIVE;
1815 let mut novelty_count = 0_usize;
1816 let mut state = slots
1817 .iter()
1818 .map(|slot| (reset_slot_flags(slot.flags), slot.generation))
1819 .collect::<Vec<_>>();
1820 for offset in 0..region.slot_count {
1821 let slot = usize::try_from(region.first_slot + offset).unwrap_or(usize::MAX);
1822 let baseline = usize::try_from(region.generation_offset + offset).unwrap_or(usize::MAX);
1823 state[slot] = (
1824 reset_slot_flags(slots[slot].flags),
1825 generation_bases[baseline],
1826 );
1827 }
1828 let wave_end = region.first_wave + region.wave_count;
1829 for wave in &waves[region.first_wave as usize..wave_end as usize] {
1830 let op_end = wave.first_op + wave.op_count;
1831 for op in &ops[wave.first_op as usize..op_end as usize] {
1832 if op.kind == OP_TEST_STATUS {
1833 decode_test_status(op)?;
1834 continue;
1835 }
1836 if op.kind == OP_TRACE_DELTA {
1837 let (_, _, semantic_guard) = decode_trace_delta(op)?;
1838 if let Some((slot, generation)) = semantic_guard {
1839 let slot_index = usize::try_from(slot).unwrap_or(usize::MAX);
1840 let slot_end = region.first_slot + region.slot_count;
1841 if slot_index >= slots.len() || slot < region.first_slot || slot >= slot_end
1842 {
1843 return Err(XlogError::Kernel(
1844 "resident schedule trace guard is outside its region scope".into(),
1845 ));
1846 }
1847 if !slot_input_is_ready(
1848 state[slot_index].0,
1849 state[slot_index].1,
1850 generation,
1851 ) {
1852 return Err(XlogError::Kernel(
1853 "resident schedule trace guard is undefined or stale".into(),
1854 ));
1855 }
1856 }
1857 continue;
1858 }
1859 let marks_novelty = op.flags & RESIDENT_SCHEDULE_OP_MARK_NOVELTY != 0;
1860 let marks_schema_winner = op.flags & RESIDENT_SCHEDULE_OP_MARK_SCHEMA_WINNER != 0;
1861 if op.kind > OP_DIFF
1862 || op.flags
1863 & !(RESIDENT_SCHEDULE_OP_MARK_NOVELTY
1864 | RESIDENT_SCHEDULE_OP_MARK_SCHEMA_WINNER)
1865 != 0
1866 || (marks_novelty && (!matches!(op.kind, OP_DIFF | OP_PROJECT) || !recursive))
1867 || op.scan_delta != 0
1868 || op.filter_delta != 0
1869 || op.reserved != 0
1870 {
1871 return Err(XlogError::Kernel(format!(
1872 "resident schedule operation {} has an unsupported kind, flag, or payload",
1873 op.op_id
1874 )));
1875 }
1876 validate_schema_winner_encoding(op, head_count)?;
1877 if marks_novelty {
1878 novelty_count += 1;
1879 }
1880 if marks_schema_winner {
1881 let candidate = &mut first_schema_candidates[op.schema_winner_head as usize];
1882 if candidate.is_none() {
1883 *candidate = Some(op.schema_winner_id);
1884 }
1885 }
1886 let uses_in0 = op.kind != OP_UNIT;
1887 let uses_in1 = matches!(op.kind, OP_JOIN_INNER | OP_JOIN_SEMI | OP_UNION | OP_DIFF);
1888 let out = usize::try_from(op.out).unwrap_or(usize::MAX);
1889 let in0 = usize::try_from(op.in0).unwrap_or(usize::MAX);
1890 let in1 = usize::try_from(op.in1).unwrap_or(usize::MAX);
1891 if out >= slots.len()
1892 || (uses_in0 && in0 >= slots.len())
1893 || (uses_in1 && in1 >= slots.len())
1894 {
1895 return Err(XlogError::Kernel(format!(
1896 "resident schedule operation {} references a slot out of range",
1897 op.op_id
1898 )));
1899 }
1900 let slot_end = region.first_slot + region.slot_count;
1901 let in_scope = |slot: u32| slot >= region.first_slot && slot < slot_end;
1902 if !in_scope(op.out)
1903 || (uses_in0 && !in_scope(op.in0))
1904 || (uses_in1 && !in_scope(op.in1))
1905 {
1906 return Err(XlogError::Kernel(format!(
1907 "resident schedule operation {} references a slot outside its region scope",
1908 op.op_id
1909 )));
1910 }
1911 if op.kind != OP_SCAN
1912 && ((uses_in0 && op.out == op.in0) || (uses_in1 && op.out == op.in1))
1913 {
1914 return Err(XlogError::Kernel(format!(
1915 "resident schedule operation {} aliases an input and output slot",
1916 op.op_id
1917 )));
1918 }
1919 if op.kind == OP_UNIT
1920 && (op.in0 != 0
1921 || op.in1 != 0
1922 || op.in0_generation != 0
1923 || op.in1_generation != 0
1924 || op.aux_offset != 0
1925 || op.aux_count != 0
1926 || op.left_key != 0
1927 || op.right_key != 0
1928 || slots[out].relation.arity != 0)
1929 {
1930 return Err(XlogError::Kernel(format!(
1931 "resident schedule unit {} has nonzero operands or invalid output",
1932 op.op_id
1933 )));
1934 }
1935 if op.kind == OP_SCAN
1936 && (op.in1 != 0
1937 || op.in1_generation != 0
1938 || op.aux_offset != 0
1939 || op.aux_count != 0
1940 || op.left_key != 0
1941 || op.right_key != 0)
1942 {
1943 return Err(XlogError::Kernel(format!(
1944 "resident schedule scan {} has nonzero operands",
1945 op.op_id
1946 )));
1947 }
1948 match op.kind {
1949 OP_UNIT | OP_SCAN => {}
1950 OP_FILTER => {
1951 if op.in1 != 0
1952 || op.in1_generation != 0
1953 || op.left_key != 0
1954 || op.right_key != 0
1955 || slots[in0].schema_tag != slots[out].schema_tag
1956 || !same_relation_layout(&slots[in0].relation, &slots[out].relation)
1957 {
1958 return Err(XlogError::Kernel(format!(
1959 "resident schedule filter {} is invalid",
1960 op.op_id
1961 )));
1962 }
1963 requirements.filter_capacity = requirements
1964 .filter_capacity
1965 .max(slots[in0].relation.capacity);
1966 }
1967 OP_PROJECT => {
1968 if op.in1 != 0
1969 || op.in1_generation != 0
1970 || op.left_key != 0
1971 || op.right_key != 0
1972 || op.aux_count != slots[out].relation.arity
1973 {
1974 return Err(XlogError::Kernel(format!(
1975 "resident schedule project {} is invalid",
1976 op.op_id
1977 )));
1978 }
1979 }
1980 OP_UNION | OP_DIFF => {
1981 if op.aux_offset != 0
1982 || op.aux_count != 0
1983 || op.left_key != 0
1984 || op.right_key != 0
1985 || slots[in0].schema_tag != slots[in1].schema_tag
1986 || slots[in0].schema_tag != slots[out].schema_tag
1987 || !same_relation_layout(&slots[in0].relation, &slots[in1].relation)
1988 || !same_relation_layout(&slots[in0].relation, &slots[out].relation)
1989 {
1990 return Err(XlogError::Kernel(format!(
1991 "resident schedule set operation {} is invalid",
1992 op.op_id
1993 )));
1994 }
1995 let candidate_capacity = u64::from(slots[in0].relation.capacity)
1996 .checked_add(u64::from(slots[in1].relation.capacity))
1997 .ok_or_else(|| {
1998 XlogError::Kernel("resident schedule set capacity overflow".into())
1999 })?;
2000 requirements.set_candidate_capacity =
2001 requirements.set_candidate_capacity.max(candidate_capacity);
2002 }
2003 OP_JOIN_INNER | OP_JOIN_SEMI => {
2004 if op.aux_offset != 0 || op.aux_count != 0 {
2005 return Err(XlogError::Kernel(format!(
2006 "resident schedule join {} has invalid auxiliary operands",
2007 op.op_id
2008 )));
2009 }
2010 let left = &slots[in0].relation;
2011 let right = &slots[in1].relation;
2012 let output = &slots[out].relation;
2013 let expected_arity = if op.kind == OP_JOIN_SEMI {
2014 left.arity
2015 } else {
2016 left.arity.checked_add(right.arity).ok_or_else(|| {
2017 XlogError::Kernel("resident schedule join arity overflow".into())
2018 })?
2019 };
2020 let left_key = usize::try_from(op.left_key).unwrap_or(usize::MAX);
2021 let right_key = usize::try_from(op.right_key).unwrap_or(usize::MAX);
2022 let keys_match = left_key < left.arity as usize
2023 && right_key < right.arity as usize
2024 && left.widths[left_key] == right.widths[right_key]
2025 && slot_types[in0].get(left_key) == slot_types[in1].get(right_key);
2026 let output_widths_match = output.arity == expected_arity
2027 && output.widths[..left.arity as usize]
2028 == left.widths[..left.arity as usize]
2029 && (op.kind == OP_JOIN_SEMI
2030 || output.widths[left.arity as usize..expected_arity as usize]
2031 == right.widths[..right.arity as usize]);
2032 let output_types_match = slot_types[out].get(..left.arity as usize)
2033 == slot_types[in0].get(..left.arity as usize)
2034 && (op.kind == OP_JOIN_SEMI
2035 || slot_types[out]
2036 .get(left.arity as usize..expected_arity as usize)
2037 == slot_types[in1].get(..right.arity as usize));
2038 if expected_arity as usize > RESIDENT_SCHEDULE_MAX_ARITY
2039 || !keys_match
2040 || !output_widths_match
2041 || !output_types_match
2042 {
2043 return Err(XlogError::Kernel(format!(
2044 "resident schedule join {} key or output schema is invalid",
2045 op.op_id
2046 )));
2047 }
2048 requirements.join_right_capacity =
2049 requirements.join_right_capacity.max(right.capacity);
2050 }
2051 _ => unreachable!("operation kind validated above"),
2052 }
2053 if uses_in0 && !slot_input_is_ready(state[in0].0, state[in0].1, op.in0_generation) {
2054 return Err(XlogError::Kernel(format!(
2055 "resident schedule operation {} reads an undefined or stale input",
2056 op.op_id
2057 )));
2058 }
2059 if uses_in1 && !slot_input_is_ready(state[in1].0, state[in1].1, op.in1_generation) {
2060 return Err(XlogError::Kernel(format!(
2061 "resident schedule operation {} reads an undefined or stale input",
2062 op.op_id
2063 )));
2064 }
2065 if op.kind == OP_SCAN {
2066 if op.out != op.in0 || op.out_generation != op.in0_generation {
2067 return Err(XlogError::Kernel(format!(
2068 "resident schedule scan {} is not an identity leaf",
2069 op.op_id
2070 )));
2071 }
2072 } else {
2073 if !slot_output_generation_is_valid(
2074 state[out].0,
2075 state[out].1,
2076 op.out_generation,
2077 ) {
2078 return Err(XlogError::Kernel(format!(
2079 "resident schedule operation {} has an invalid output generation",
2080 op.op_id
2081 )));
2082 }
2083 state[out].0 = finish_slot_write(state[out].0, true);
2084 state[out].1 = op.out_generation;
2085 }
2086 }
2087 }
2088 if recursive && novelty_count == 0 {
2089 return Err(XlogError::Kernel(
2090 "resident schedule recursive body has no marked novelty output".into(),
2091 ));
2092 }
2093 }
2094 for (head, (&default, candidate)) in schema_defaults
2095 .iter()
2096 .zip(first_schema_candidates)
2097 .enumerate()
2098 {
2099 if candidate != Some(default) {
2100 return Err(XlogError::Kernel(format!(
2101 "resident schedule schema default for head {head} does not match its first candidate"
2102 )));
2103 }
2104 }
2105 Ok(requirements)
2106}
2107
2108fn buffers_share_storage(left: &CudaBuffer, right: &CudaBuffer) -> bool {
2109 fn allocation_aliases(
2110 left_ptr: u64,
2111 left_len: u64,
2112 left_block: Option<&crate::device_runtime::DeviceBlock>,
2113 right_ptr: u64,
2114 right_len: u64,
2115 right_block: Option<&crate::device_runtime::DeviceBlock>,
2116 ) -> bool {
2117 let same_runtime_allocation = match (left_block, right_block) {
2118 (Some(left), Some(right)) => {
2119 left.ptr == right.ptr && left.generation == right.generation
2120 }
2121 _ => false,
2122 };
2123 same_runtime_allocation || device_ranges_overlap(left_ptr, left_len, right_ptr, right_len)
2124 }
2125
2126 let left_count_aliases_right_count = allocation_aliases(
2127 left.num_rows_device().device_ptr_value(),
2128 std::mem::size_of::<u32>() as u64,
2129 left.num_rows_device().runtime_block(),
2130 right.num_rows_device().device_ptr_value(),
2131 std::mem::size_of::<u32>() as u64,
2132 right.num_rows_device().runtime_block(),
2133 );
2134 let left_count_aliases_right_column = right.columns().iter().any(|right_column| {
2135 allocation_aliases(
2136 left.num_rows_device().device_ptr_value(),
2137 std::mem::size_of::<u32>() as u64,
2138 left.num_rows_device().runtime_block(),
2139 *right_column.device_ptr(),
2140 u64::try_from(right_column.len()).unwrap_or(u64::MAX),
2141 right_column.runtime_block(),
2142 )
2143 });
2144 let left_column_aliases_right_count = left.columns().iter().any(|left_column| {
2145 allocation_aliases(
2146 *left_column.device_ptr(),
2147 u64::try_from(left_column.len()).unwrap_or(u64::MAX),
2148 left_column.runtime_block(),
2149 right.num_rows_device().device_ptr_value(),
2150 std::mem::size_of::<u32>() as u64,
2151 right.num_rows_device().runtime_block(),
2152 )
2153 });
2154 let columns_alias = left.columns().iter().any(|left_column| {
2155 right.columns().iter().any(|right_column| {
2156 allocation_aliases(
2157 *left_column.device_ptr(),
2158 u64::try_from(left_column.len()).unwrap_or(u64::MAX),
2159 left_column.runtime_block(),
2160 *right_column.device_ptr(),
2161 u64::try_from(right_column.len()).unwrap_or(u64::MAX),
2162 right_column.runtime_block(),
2163 )
2164 })
2165 });
2166 left_count_aliases_right_count
2167 || left_count_aliases_right_column
2168 || left_column_aliases_right_count
2169 || columns_alias
2170}
2171
2172fn device_ranges_overlap(left_ptr: u64, left_len: u64, right_ptr: u64, right_len: u64) -> bool {
2173 if left_len == 0 || right_len == 0 {
2174 return false;
2175 }
2176 let left_end = left_ptr.saturating_add(left_len);
2177 let right_end = right_ptr.saturating_add(right_len);
2178 left_ptr < right_end && right_ptr < left_end
2179}
2180
2181fn relation_view(buffer: &CudaBuffer) -> Result<(ResidentRelationView, u32)> {
2182 if buffer.arity() > RESIDENT_SCHEDULE_MAX_ARITY {
2183 return Err(XlogError::Kernel(format!(
2184 "resident schedule relation arity {} exceeds {RESIDENT_SCHEDULE_MAX_ARITY}",
2185 buffer.arity()
2186 )));
2187 }
2188 let capacity = checked_capacity(buffer.num_rows(), "relation")?;
2189 let mut columns = [0; RESIDENT_SCHEDULE_MAX_ARITY];
2190 let mut widths = [0; RESIDENT_SCHEDULE_MAX_ARITY];
2191 let mut schema_tag = 2_166_136_261_u32;
2192 schema_tag ^= checked_u32(buffer.arity(), "arity")?;
2193 schema_tag = schema_tag.wrapping_mul(16_777_619);
2194 for column in 0..buffer.arity() {
2195 columns[column] = *buffer.column(column).expect("arity checked").device_ptr();
2196 let scalar = buffer
2197 .schema()
2198 .column_type(column)
2199 .expect("schema arity checked");
2200 let width = resident_schedule_scalar_width(scalar)?;
2201 widths[column] = width;
2202 schema_tag ^= resident_schedule_scalar_tag(scalar);
2203 schema_tag = schema_tag.wrapping_mul(16_777_619);
2204 }
2205 Ok((
2206 ResidentRelationView {
2207 columns,
2208 widths,
2209 arity: checked_u32(buffer.arity(), "arity")?,
2210 capacity,
2211 reserved: 0,
2212 num_rows: buffer.num_rows_device().device_ptr_value(),
2213 },
2214 schema_tag,
2215 ))
2216}
2217
2218fn relation_scalar_types(buffer: &CudaBuffer) -> Vec<ScalarType> {
2219 (0..buffer.arity())
2220 .map(|column| {
2221 buffer
2222 .schema()
2223 .column_type(column)
2224 .expect("buffer schema arity")
2225 })
2226 .collect()
2227}
2228
2229fn finalize_schedule_output_counts(
2230 provider: &CudaKernelProvider,
2231 relations: &[ResidentScheduleRelation<'_>],
2232 receipt_slots: &[u32],
2233 counts: &[u32],
2234) -> Result<()> {
2235 if counts.len() != receipt_slots.len() {
2236 return Err(XlogError::Kernel(
2237 "resident schedule receipt count table is truncated".into(),
2238 ));
2239 }
2240 let mut entries = Vec::new();
2241 for (slot, relation) in relations.iter().enumerate() {
2242 if !relation.is_output() {
2243 continue;
2244 }
2245 let receipt_index = receipt_slots
2246 .iter()
2247 .position(|candidate| *candidate == slot as u32)
2248 .ok_or_else(|| {
2249 XlogError::Kernel(format!(
2250 "resident schedule output slot {slot} has no receipt field"
2251 ))
2252 })?;
2253 entries.push((relation.buffer(), counts[receipt_index]));
2254 }
2255 provider.finalize_resident_logical_counts(&entries)
2256}
2257
2258impl CudaKernelProvider {
2259 pub fn bind_resident_execution_domain(
2260 &self,
2261 runtime: Arc<XlogDeviceRuntime>,
2262 stream_id: StreamId,
2263 stream: Arc<CudaStream>,
2264 ) -> Result<ResidentExecutionDomain> {
2265 let manager_runtime = self.memory().runtime().ok_or_else(|| {
2266 XlogError::Kernel(
2267 "resident execution domain requires a runtime-backed memory manager".into(),
2268 )
2269 })?;
2270 if !Arc::ptr_eq(manager_runtime, &runtime)
2271 || !Arc::ptr_eq(self.device(), self.memory().device())
2272 || !Arc::ptr_eq(self.device(), runtime.device())
2273 {
2274 return Err(XlogError::Kernel(
2275 "resident execution domain provider, manager, and runtime identities differ".into(),
2276 ));
2277 }
2278 let device_ordinal = u32::try_from(self.device().ordinal()).map_err(|_| {
2279 XlogError::Kernel("resident execution domain device ordinal overflow".into())
2280 })?;
2281 if runtime.device_ordinal() != device_ordinal || !runtime.supports_block_use_tracking() {
2282 return Err(XlogError::Kernel(
2283 "resident execution domain runtime is incompatible with the provider".into(),
2284 ));
2285 }
2286 let resolved_stream = runtime.stream_pool().resolve(stream_id).ok_or_else(|| {
2287 XlogError::Kernel(
2288 "resident execution domain stream id is not owned by the runtime".into(),
2289 )
2290 })?;
2291 if !Arc::ptr_eq(&resolved_stream, &stream) {
2292 return Err(XlogError::Kernel(
2293 "resident execution domain stream does not match its runtime stream id".into(),
2294 ));
2295 }
2296 let provider_context = self.device().inner().stream().context();
2297 if !Arc::ptr_eq(stream.context(), provider_context)
2298 || stream.context().cu_ctx() != provider_context.cu_ctx()
2299 {
2300 return Err(XlogError::Kernel(
2301 "resident execution domain stream belongs to a foreign CUDA context".into(),
2302 ));
2303 }
2304 Ok(ResidentExecutionDomain {
2305 provider_identity: self.provider_identity(),
2306 memory_manager_identity: Arc::as_ptr(self.memory()) as usize,
2307 runtime,
2308 stream_id,
2309 stream,
2310 context: Arc::clone(provider_context),
2311 marker: Arc::new(()),
2312 })
2313 }
2314
2315 fn upload_resident_schedule_metadata<T>(&self, values: &[T]) -> Result<TrackedCudaSlice<T>>
2316 where
2317 T: DeviceRepr + Default + Copy,
2318 {
2319 let mut allocation = self.memory.alloc::<T>(values.len().max(1))?;
2320 if values.is_empty() {
2321 self.htod_launch_metadata_sync_copy_into(&[T::default()], &mut allocation)?;
2322 } else {
2323 self.htod_launch_metadata_sync_copy_into(values, &mut allocation)?;
2324 }
2325 Ok(allocation)
2326 }
2327
2328 fn upload_resident_schedule_metadata_in_reservation<T>(
2329 &self,
2330 values: &[T],
2331 reservation: &mut GpuMemoryReservation,
2332 ) -> Result<TrackedCudaSlice<T>>
2333 where
2334 T: DeviceRepr + Default + Copy,
2335 {
2336 let mut allocation = reservation.alloc::<T>(values.len().max(1))?;
2337 if values.is_empty() {
2338 self.htod_launch_metadata_sync_copy_into(&[T::default()], &mut allocation)?;
2339 } else {
2340 self.htod_launch_metadata_sync_copy_into(values, &mut allocation)?;
2341 }
2342 Ok(allocation)
2343 }
2344
2345 #[allow(clippy::too_many_arguments)]
2347 pub fn prepare_resident_schedule<'a>(
2348 &self,
2349 mut relations: Vec<ResidentScheduleRelation<'a>>,
2350 ops: &[ResidentOpDescriptor],
2351 waves: &[ResidentWaveDescriptor],
2352 regions: &[ResidentRegionDescriptor],
2353 filter_comparisons: &[ResidentFilterComparisonDescriptor],
2354 project_expressions: &[ResidentProjectExpressionDescriptor],
2355 receipt_slots: &[u32],
2356 ) -> Result<ResidentSchedule<'a>> {
2357 let slot_count = checked_u32(relations.len(), "slot count")?;
2358 let op_count = checked_u32(ops.len(), "operation count")?;
2359 let wave_count = checked_u32(waves.len(), "wave count")?;
2360 let region_count = checked_u32(regions.len(), "region count")?;
2361 let filter_comparison_count =
2362 checked_u32(filter_comparisons.len(), "filter comparison count")?;
2363 let project_expression_count =
2364 checked_u32(project_expressions.len(), "project expression count")?;
2365
2366 let mut slot_descriptors = Vec::with_capacity(relations.len());
2367 let mut slot_types = Vec::with_capacity(relations.len());
2368 let mut max_capacity = 0_u32;
2369 let provider_manager = Arc::as_ptr(&self.memory) as usize;
2370 let provider_context = self.device.inner().stream().context().cu_ctx();
2371 let provider_ordinal = self.device.ordinal() as u32;
2372 for relation in &mut relations {
2373 let buffer = relation.buffer();
2374 if buffer.num_rows_device().memory_manager_ptr_value() != provider_manager {
2375 return Err(XlogError::Kernel(
2376 "resident schedule relation belongs to a foreign provider".into(),
2377 ));
2378 }
2379 for column in buffer.columns() {
2380 if column.stream().context().cu_ctx() != provider_context
2381 || column
2382 .runtime_block()
2383 .is_some_and(|block| block.device_ordinal != provider_ordinal)
2384 {
2385 return Err(XlogError::Kernel(
2386 "resident schedule relation belongs to a foreign CUDA context".into(),
2387 ));
2388 }
2389 }
2390 let (view, schema_tag) = relation_view(buffer)?;
2391 if u64::from(relation.initial_count()) > buffer.num_rows() {
2392 return Err(XlogError::Kernel(format!(
2393 "resident schedule initial count {} exceeds capacity {}",
2394 relation.initial_count(),
2395 buffer.num_rows()
2396 )));
2397 }
2398 max_capacity = max_capacity.max(view.capacity);
2399 slot_descriptors.push(ResidentRelationSlot {
2400 relation: view,
2401 generation: relation.generation(),
2402 flags: relation.flags(),
2403 initial_count: relation.initial_count(),
2404 schema_tag,
2405 });
2406 slot_types.push(relation_scalar_types(buffer));
2407 }
2408 for (output_slot, output) in relations.iter().enumerate() {
2409 if !output.is_output() {
2410 continue;
2411 }
2412 for (other_slot, other) in relations.iter().enumerate() {
2413 if output_slot != other_slot
2414 && buffers_share_storage(output.buffer(), other.buffer())
2415 {
2416 return Err(XlogError::Kernel(format!(
2417 "resident schedule output slot {output_slot} aliases storage in slot {other_slot}"
2418 )));
2419 }
2420 }
2421 }
2422
2423 let mut region_descriptors = regions.to_vec();
2424 let slot_generations = slot_descriptors
2425 .iter()
2426 .map(|slot| slot.generation)
2427 .collect::<Vec<_>>();
2428 let generation_base_values =
2429 build_generation_baselines(&mut region_descriptors, &slot_generations)?;
2430 let generation_base_count =
2431 checked_u32(generation_base_values.len(), "generation baseline count")?;
2432 validate_generation_baseline_ranges(®ion_descriptors, generation_base_count)?;
2433
2434 let requirements = validate_schedule_program(
2435 &slot_descriptors,
2436 &slot_types,
2437 ops,
2438 waves,
2439 ®ion_descriptors,
2440 &generation_base_values,
2441 filter_comparisons,
2442 project_expressions,
2443 &[],
2444 )?;
2445 let filter_capacity = requirements.filter_capacity;
2446 let set_candidate_capacity = requirements.set_candidate_capacity;
2447 let join_right_capacity = requirements.join_right_capacity;
2448 let set_slot_count = checked_workspace_slots(set_candidate_capacity, "set workspace")?;
2449 let join_bucket_count =
2450 checked_workspace_slots(u64::from(join_right_capacity), "join workspace")?;
2451 let filter_block_count = filter_capacity
2452 .div_ceil(RESIDENT_SCHEDULE_BLOCK_SIZE)
2453 .max(1);
2454 let requested_receipt_count = receipt_slots.len();
2455 let mut all_receipt_slots = receipt_slots.to_vec();
2456 for (slot, relation) in relations.iter().enumerate() {
2457 let slot = checked_u32(slot, "receipt slot")?;
2458 if relation.is_output() && !all_receipt_slots.contains(&slot) {
2459 all_receipt_slots.push(slot);
2460 }
2461 }
2462 let receipt_count = checked_u32(all_receipt_slots.len(), "receipt count")?;
2463 let receipt_byte_count = std::mem::size_of::<ResidentTerminalStatus>()
2464 .checked_add(
2465 std::mem::size_of::<u32>()
2466 .checked_mul(all_receipt_slots.len() + 1)
2467 .ok_or_else(|| {
2468 XlogError::Kernel("resident schedule receipt overflow".into())
2469 })?,
2470 )
2471 .ok_or_else(|| XlogError::Kernel("resident schedule receipt overflow".into()))?;
2472 let receipt_byte_count_u32 = checked_u32(receipt_byte_count, "receipt byte count")?;
2473 let mut receipt_count_ptrs = Vec::with_capacity(all_receipt_slots.len());
2474 for &slot in &all_receipt_slots {
2475 let index = usize::try_from(slot).unwrap_or(usize::MAX);
2476 let relation = relations.get(index).ok_or_else(|| {
2477 XlogError::Kernel(format!(
2478 "resident schedule receipt slot {slot} is out of range"
2479 ))
2480 })?;
2481 receipt_count_ptrs.push(relation.buffer().num_rows_device().device_ptr_value());
2482 }
2483
2484 let function = self
2485 .device()
2486 .inner()
2487 .get_func(MODULE, KERNEL)
2488 .ok_or_else(|| XlogError::Kernel("resident_schedule_execute kernel missing".into()))?;
2489 let cooperative = self
2490 .device()
2491 .inner()
2492 .attribute(sys::CUdevice_attribute::CU_DEVICE_ATTRIBUTE_COOPERATIVE_LAUNCH)
2493 .map_err(|error| XlogError::Kernel(format!("query cooperative launch: {error}")))?;
2494 if cooperative == 0 {
2495 return Err(XlogError::Kernel(
2496 "CUDA device does not support cooperative kernel launch".into(),
2497 ));
2498 }
2499 let active_per_sm = function
2500 .occupancy_max_active_blocks_per_multiprocessor(RESIDENT_SCHEDULE_BLOCK_SIZE, 0, None)
2501 .map_err(|error| XlogError::Kernel(format!("resident schedule occupancy: {error}")))?;
2502 let multiprocessors = self
2503 .device()
2504 .inner()
2505 .attribute(sys::CUdevice_attribute::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
2506 .map_err(|error| XlogError::Kernel(format!("query multiprocessor count: {error}")))?;
2507 let cooperative_limit =
2508 active_per_sm
2509 .checked_mul(u32::try_from(multiprocessors).map_err(|_| {
2510 XlogError::Kernel("CUDA multiprocessor count is invalid".into())
2511 })?)
2512 .ok_or_else(|| XlogError::Kernel("resident schedule grid overflow".into()))?;
2513 if cooperative_limit == 0 {
2514 return Err(XlogError::Kernel(
2515 "resident schedule has zero cooperative occupancy".into(),
2516 ));
2517 }
2518 let required_blocks = max_capacity
2519 .max(filter_capacity)
2520 .div_ceil(RESIDENT_SCHEDULE_BLOCK_SIZE)
2521 .max(1);
2522 let launch_config = LaunchConfig {
2523 grid_dim: (required_blocks.min(cooperative_limit), 1, 1),
2524 block_dim: (RESIDENT_SCHEDULE_BLOCK_SIZE, 1, 1),
2525 shared_mem_bytes: 0,
2526 };
2527
2528 let slots = self.upload_resident_schedule_metadata(&slot_descriptors)?;
2529 let ops = self.upload_resident_schedule_metadata(ops)?;
2530 let waves = self.upload_resident_schedule_metadata(waves)?;
2531 let regions = self.upload_resident_schedule_metadata(®ion_descriptors)?;
2532 let generation_bases = self.upload_resident_schedule_metadata(&generation_base_values)?;
2533 let filter_comparisons = self.upload_resident_schedule_metadata(filter_comparisons)?;
2534 let project_expressions = self.upload_resident_schedule_metadata(project_expressions)?;
2535 let filter_mask = self
2536 .memory
2537 .alloc::<u32>(usize::try_from(filter_capacity).unwrap_or(0).max(1))?;
2538 let filter_prefix = self
2539 .memory
2540 .alloc::<u32>(usize::try_from(filter_capacity).unwrap_or(0).max(1))?;
2541 let filter_block_sums = self
2542 .memory
2543 .alloc::<u32>(usize::try_from(filter_block_count).unwrap_or(1))?;
2544 let filter_block_offsets = self
2545 .memory
2546 .alloc::<u32>(usize::try_from(filter_block_count).unwrap_or(1))?;
2547 let set_slots = self
2548 .memory
2549 .alloc::<u64>(usize::try_from(set_slot_count).unwrap_or(1))?;
2550 let set_required = self.memory.alloc::<u64>(1)?;
2551 let join_buckets = self
2552 .memory
2553 .alloc::<u32>(usize::try_from(join_bucket_count).unwrap_or(1))?;
2554 let join_next = self
2555 .memory
2556 .alloc::<u32>(usize::try_from(join_right_capacity).unwrap_or(0).max(1))?;
2557 let join_required = self.memory.alloc::<u64>(1)?;
2558 let status =
2559 self.upload_resident_schedule_metadata(&[ResidentTerminalStatus::default()])?;
2560 let changed = self.upload_resident_schedule_metadata(&[0_u32])?;
2561 let iterations = self.upload_resident_schedule_metadata(&[0_u32])?;
2562 let scan_trace = self.upload_resident_schedule_metadata(&[0_u32])?;
2563 let filter_trace = self.upload_resident_schedule_metadata(&[0_u32])?;
2564 let semantic_scan_trace = self.upload_resident_schedule_metadata(&[0_u32])?;
2565 let semantic_filter_trace = self.upload_resident_schedule_metadata(&[0_u32])?;
2566 let receipt_table = self.upload_resident_schedule_metadata(&receipt_count_ptrs)?;
2567 let receipt_bytes = self.memory.alloc::<u8>(receipt_byte_count.max(1))?;
2568 let pinned_receipt = ResidentSchedulePinnedReceipt::allocate(receipt_byte_count)?;
2569
2570 let header_value = ResidentScheduleHeader {
2571 slots: slots.device_ptr_value(),
2572 ops: ops.device_ptr_value(),
2573 waves: waves.device_ptr_value(),
2574 regions: regions.device_ptr_value(),
2575 generation_metadata: generation_bases.device_ptr_value(),
2576 filter_comparisons: filter_comparisons.device_ptr_value(),
2577 project_expressions: project_expressions.device_ptr_value(),
2578 filter_mask: filter_mask.device_ptr_value(),
2579 filter_prefix: filter_prefix.device_ptr_value(),
2580 filter_block_sums: filter_block_sums.device_ptr_value(),
2581 filter_block_offsets: filter_block_offsets.device_ptr_value(),
2582 set_slots: set_slots.device_ptr_value(),
2583 set_required: set_required.device_ptr_value(),
2584 join_buckets: join_buckets.device_ptr_value(),
2585 join_next: join_next.device_ptr_value(),
2586 join_required: join_required.device_ptr_value(),
2587 status: status.device_ptr_value(),
2588 changed: changed.device_ptr_value(),
2589 iterations: iterations.device_ptr_value(),
2590 scan_trace: scan_trace.device_ptr_value(),
2591 filter_trace: filter_trace.device_ptr_value(),
2592 semantic_scan_trace: semantic_scan_trace.device_ptr_value(),
2593 semantic_filter_trace: semantic_filter_trace.device_ptr_value(),
2594 schema_seen_nonempty: 0,
2595 schema_winner_ids: 0,
2596 receipt_table: receipt_table.device_ptr_value(),
2597 receipt_bytes: receipt_bytes.device_ptr_value(),
2598 slot_count,
2599 op_count,
2600 wave_count,
2601 region_count,
2602 filter_comparison_count,
2603 project_expression_count,
2604 filter_capacity,
2605 filter_block_count,
2606 set_slot_mask: set_slot_count - 1,
2607 set_candidate_capacity: u32::try_from(set_candidate_capacity).map_err(|_| {
2608 XlogError::Kernel("resident schedule set capacity exceeds u32".into())
2609 })?,
2610 join_bucket_mask: join_bucket_count - 1,
2611 join_right_capacity,
2612 schema_winner_count: 0,
2613 receipt_count,
2614 receipt_byte_count: receipt_byte_count_u32,
2615 generation_metadata_count: generation_base_count,
2616 abi_version: RESIDENT_SCHEDULE_ABI_VERSION,
2617 reserved: 0,
2618 };
2619 let header = self.upload_resident_schedule_metadata(&[header_value])?;
2620 for relation in &mut relations {
2621 relation.invalidate_output_metadata();
2622 }
2623
2624 Ok(ResidentSchedule {
2625 origin_provider_identity: self.provider_identity(),
2626 origin_memory_manager: Arc::as_ptr(&self.memory) as usize,
2627 header,
2628 _slots: slots,
2629 _ops: ops,
2630 _waves: waves,
2631 _regions: regions,
2632 _generation_metadata: generation_bases,
2633 _filter_comparisons: filter_comparisons,
2634 _project_expressions: project_expressions,
2635 _filter_mask: filter_mask,
2636 _filter_prefix: filter_prefix,
2637 _filter_block_sums: filter_block_sums,
2638 _filter_block_offsets: filter_block_offsets,
2639 _set_slots: set_slots,
2640 _set_required: set_required,
2641 _join_buckets: join_buckets,
2642 _join_next: join_next,
2643 _join_required: join_required,
2644 _status: status,
2645 _changed: changed,
2646 _iterations: iterations,
2647 _scan_trace: scan_trace,
2648 _filter_trace: filter_trace,
2649 _semantic_scan_trace: semantic_scan_trace,
2650 _semantic_filter_trace: semantic_filter_trace,
2651 _receipt_table: receipt_table,
2652 receipt_bytes,
2653 pinned_receipt,
2654 launch_config,
2655 region_count,
2656 region_descriptors,
2657 requested_receipt_count,
2658 receipt_slots: all_receipt_slots,
2659 relations,
2660 })
2661 }
2662
2663 #[allow(clippy::too_many_arguments)]
2667 pub fn prepare_resident_schedule_program_in_reservation<'a>(
2668 &self,
2669 domain: &ResidentExecutionDomain,
2670 bindings: &[ResidentScheduleSlotBinding<'a>],
2671 ops: &[ResidentOpDescriptor],
2672 waves: &[ResidentWaveDescriptor],
2673 regions: &[ResidentRegionDescriptor],
2674 generation_bases: &[u32],
2675 filter_comparisons: &[ResidentFilterComparisonDescriptor],
2676 project_expressions: &[ResidentProjectExpressionDescriptor],
2677 receipt_slots: &[u32],
2678 external: ResidentScheduleExternalBindings<'a>,
2679 reservation: &mut GpuMemoryReservation,
2680 ) -> Result<ResidentScheduleDeviceProgram> {
2681 validate_execution_domain(self, domain)?;
2682 if reservation.memory_manager_ptr_value() != domain.memory_manager_identity {
2683 return Err(XlogError::Kernel(
2684 "resident schedule reservation belongs to a foreign memory manager".into(),
2685 ));
2686 }
2687 let slot_count = checked_u32(bindings.len(), "slot count")?;
2688 let op_count = checked_u32(ops.len(), "op count")?;
2689 let wave_count = checked_u32(waves.len(), "wave count")?;
2690 let region_count = checked_u32(regions.len(), "region count")?;
2691 let generation_base_count =
2692 checked_u32(generation_bases.len(), "generation baseline count")?;
2693 let filter_comparison_count =
2694 checked_u32(filter_comparisons.len(), "filter comparison count")?;
2695 let project_expression_count =
2696 checked_u32(project_expressions.len(), "project expression count")?;
2697
2698 if bindings.is_empty() {
2699 return Err(XlogError::Kernel(
2700 "resident schedule program requires slots".into(),
2701 ));
2702 }
2703 validate_region_control_and_ranges(regions, wave_count, slot_count)?;
2704 validate_generation_baseline_ranges(regions, generation_base_count)?;
2705 validate_wave_partition(waves, op_count)?;
2706
2707 let mut slot_descriptors = Vec::with_capacity(bindings.len());
2708 let mut slot_types = Vec::with_capacity(bindings.len());
2709 let mut slot_count_identities = Vec::with_capacity(bindings.len());
2710 let mut allocation_ranges = Vec::new();
2711 let mut max_capacity = 0_u32;
2712 for binding in bindings {
2713 for column in binding.buffer().columns() {
2714 validate_schedule_allocation(
2715 column.runtime_allocation_identity()?,
2716 domain,
2717 &mut allocation_ranges,
2718 )?;
2719 }
2720 slot_count_identities.push(validate_schedule_allocation(
2721 binding
2722 .buffer()
2723 .num_rows_device()
2724 .runtime_allocation_identity()?,
2725 domain,
2726 &mut allocation_ranges,
2727 )?);
2728 let (relation, schema_tag) = relation_view(binding.buffer())?;
2729 max_capacity = max_capacity.max(relation.capacity);
2730 slot_descriptors.push(ResidentRelationSlot {
2731 relation,
2732 generation: binding.generation(),
2733 flags: binding.flags(),
2734 initial_count: binding.initial_count(),
2735 schema_tag,
2736 });
2737 slot_types.push(relation_scalar_types(binding.buffer()));
2738 }
2739
2740 let (receipt_table, receipt_bytes, receipt_count, receipt_byte_count) =
2741 external.receipt.schedule_parts();
2742 let (schema_seen_nonempty, schema_winner_ids, schema_winner_count) =
2743 external.schema_winners.schedule_parts();
2744 let head_count =
2745 checked_schedule_winner_count(receipt_count, receipt_byte_count, schema_winner_count)?;
2746 let schema_defaults = external.schema_winners.default_schema_ids();
2747 if schema_defaults.len() != head_count as usize {
2748 return Err(XlogError::Kernel(
2749 "resident schedule schema-default count differs from the receipt".into(),
2750 ));
2751 }
2752 let generation_metadata = build_generation_metadata(generation_bases, schema_defaults)?;
2753 let generation_metadata_count =
2754 checked_u32(generation_metadata.len(), "generation metadata count")?;
2755 let requirements = validate_schedule_program(
2756 &slot_descriptors,
2757 &slot_types,
2758 ops,
2759 waves,
2760 regions,
2761 generation_bases,
2762 filter_comparisons,
2763 project_expressions,
2764 schema_defaults,
2765 )?;
2766
2767 if let Some(filter_scratch) = external.filter_scratch {
2768 for snapshot in filter_scratch.schedule_owner_snapshots()? {
2769 validate_schedule_allocation(snapshot, domain, &mut allocation_ranges)?;
2770 }
2771 }
2772 for snapshot in external.set_workspace.schedule_owner_snapshots()? {
2773 validate_schedule_allocation(snapshot, domain, &mut allocation_ranges)?;
2774 }
2775 for snapshot in external.join_workspace.schedule_owner_snapshots()? {
2776 validate_schedule_allocation(snapshot, domain, &mut allocation_ranges)?;
2777 }
2778 for snapshot in external.control.schedule_owner_snapshots()? {
2779 validate_schedule_allocation(snapshot, domain, &mut allocation_ranges)?;
2780 }
2781 let [scan_trace_snapshot, filter_trace_snapshot, semantic_scan_trace_snapshot, semantic_filter_trace_snapshot] =
2782 external.trace.schedule_owner_snapshots()?;
2783 let scan_trace_identity =
2784 validate_schedule_allocation(scan_trace_snapshot, domain, &mut allocation_ranges)?;
2785 let filter_trace_identity =
2786 validate_schedule_allocation(filter_trace_snapshot, domain, &mut allocation_ranges)?;
2787 let semantic_scan_trace_identity = validate_schedule_allocation(
2788 semantic_scan_trace_snapshot,
2789 domain,
2790 &mut allocation_ranges,
2791 )?;
2792 let semantic_filter_trace_identity = validate_schedule_allocation(
2793 semantic_filter_trace_snapshot,
2794 domain,
2795 &mut allocation_ranges,
2796 )?;
2797 let [schema_seen_snapshot, schema_winner_snapshot] =
2798 external.schema_winners.schedule_owner_snapshots()?;
2799 validate_schedule_allocation(schema_seen_snapshot, domain, &mut allocation_ranges)?;
2800 let schema_winner_identity =
2801 validate_schedule_allocation(schema_winner_snapshot, domain, &mut allocation_ranges)?;
2802 for snapshot in external.receipt.schedule_owner_snapshots()? {
2803 validate_schedule_allocation(snapshot, domain, &mut allocation_ranges)?;
2804 }
2805
2806 if external.receipt.relation_count_len() != head_count
2807 || external.receipt.device_trace_field_count() != 4
2808 || external.receipt.schema_winner_count() != head_count
2809 || external.receipt.total_count_field_len() != receipt_count
2810 {
2811 return Err(XlogError::Kernel(
2812 "resident schedule receipt owner shape differs from the header".into(),
2813 ));
2814 }
2815 let slot_flags: Vec<u32> = slot_descriptors.iter().map(|slot| slot.flags).collect();
2816 let receipt_slot_indices =
2817 validate_receipt_slot_mapping(receipt_slots, &slot_flags, head_count)?;
2818 let mut relation_count_ptrs = Vec::with_capacity(receipt_slot_indices.len());
2819 let expected_block_count = receipt_slot_indices
2820 .len()
2821 .checked_mul(2)
2822 .and_then(|count| count.checked_add(4))
2823 .ok_or_else(|| XlogError::Kernel("resident receipt block count overflow".into()))?;
2824 let mut expected_receipt_blocks = Vec::with_capacity(expected_block_count);
2825 for slot in receipt_slot_indices {
2826 relation_count_ptrs.push(bindings[slot].buffer().num_rows_device().device_ptr_value());
2827 expected_receipt_blocks.push(slot_count_identities[slot].block_id);
2828 }
2829 let (scan_trace, filter_trace, semantic_scan_trace, semantic_filter_trace) =
2830 external.trace.schedule_parts();
2831 expected_receipt_blocks.push(scan_trace_identity.block_id);
2832 expected_receipt_blocks.push(filter_trace_identity.block_id);
2833 expected_receipt_blocks.push(semantic_scan_trace_identity.block_id);
2834 expected_receipt_blocks.push(semantic_filter_trace_identity.block_id);
2835 let mut schema_winner_ptrs = Vec::with_capacity(receipt_slots.len());
2836 for index in 0..head_count {
2837 let offset = u64::from(index)
2838 .checked_mul(u64::try_from(std::mem::size_of::<u32>()).map_err(|_| {
2839 XlogError::Kernel("resident schema-winner element size overflow".into())
2840 })?)
2841 .ok_or_else(|| {
2842 XlogError::Kernel("resident schema-winner offset overflow".into())
2843 })?;
2844 schema_winner_ptrs.push(schema_winner_ids.checked_add(offset).ok_or_else(|| {
2845 XlogError::Kernel("resident schema-winner pointer overflow".into())
2846 })?);
2847 expected_receipt_blocks.push(schema_winner_identity.block_id);
2848 }
2849 external.receipt.validate_schedule_pointees(
2850 domain.memory_manager_identity,
2851 domain.runtime.device_ordinal(),
2852 &relation_count_ptrs,
2853 [
2854 scan_trace,
2855 filter_trace,
2856 semantic_scan_trace,
2857 semantic_filter_trace,
2858 ],
2859 &schema_winner_ptrs,
2860 &expected_receipt_blocks,
2861 )?;
2862
2863 let filter_capacity = requirements.filter_capacity;
2864 let set_candidate_capacity = requirements.set_candidate_capacity;
2865 let join_right_capacity = requirements.join_right_capacity;
2866
2867 let (
2868 filter_mask,
2869 filter_prefix,
2870 filter_block_sums,
2871 filter_block_offsets,
2872 supplied_filter_capacity,
2873 filter_block_count,
2874 ) = match external.filter_scratch {
2875 Some(scratch) => scratch.schedule_parts(),
2876 None if filter_capacity == 0 => (0, 0, 0, 0, 0, 0),
2877 None => {
2878 return Err(XlogError::Kernel(
2879 "resident schedule filter scratch is missing".into(),
2880 ));
2881 }
2882 };
2883 if supplied_filter_capacity < filter_capacity {
2884 return Err(XlogError::Kernel(
2885 "resident schedule filter scratch is undersized".into(),
2886 ));
2887 }
2888 let (set_slots, set_required, set_slot_mask, supplied_set_capacity) =
2889 external.set_workspace.schedule_parts();
2890 if u64::from(supplied_set_capacity) < set_candidate_capacity {
2891 return Err(XlogError::Kernel(
2892 "resident schedule set workspace is undersized".into(),
2893 ));
2894 }
2895 let (join_buckets, join_next, join_required, join_bucket_mask, supplied_join_capacity) =
2896 external.join_workspace.schedule_parts();
2897 if supplied_join_capacity < join_right_capacity {
2898 return Err(XlogError::Kernel(
2899 "resident schedule join workspace is undersized".into(),
2900 ));
2901 }
2902 let function = self
2903 .device()
2904 .inner()
2905 .get_func(MODULE, KERNEL)
2906 .ok_or_else(|| XlogError::Kernel("resident_schedule_execute kernel missing".into()))?;
2907 let active_per_sm = function
2908 .occupancy_max_active_blocks_per_multiprocessor(RESIDENT_SCHEDULE_BLOCK_SIZE, 0, None)
2909 .map_err(|error| XlogError::Kernel(format!("resident schedule occupancy: {error}")))?;
2910 let multiprocessors = self
2911 .device()
2912 .inner()
2913 .attribute(sys::CUdevice_attribute::CU_DEVICE_ATTRIBUTE_MULTIPROCESSOR_COUNT)
2914 .map_err(|error| XlogError::Kernel(format!("query multiprocessor count: {error}")))?;
2915 let cooperative_limit =
2916 active_per_sm
2917 .checked_mul(u32::try_from(multiprocessors).map_err(|_| {
2918 XlogError::Kernel("CUDA multiprocessor count is invalid".into())
2919 })?)
2920 .ok_or_else(|| XlogError::Kernel("resident schedule grid overflow".into()))?;
2921 if cooperative_limit == 0 {
2922 return Err(XlogError::Kernel(
2923 "resident schedule has zero cooperative occupancy".into(),
2924 ));
2925 }
2926 let launch_config = LaunchConfig {
2927 grid_dim: (
2928 max_capacity
2929 .div_ceil(RESIDENT_SCHEDULE_BLOCK_SIZE)
2930 .max(1)
2931 .min(cooperative_limit),
2932 1,
2933 1,
2934 ),
2935 block_dim: (RESIDENT_SCHEDULE_BLOCK_SIZE, 1, 1),
2936 shared_mem_bytes: 0,
2937 };
2938
2939 let required_metadata_bytes = resident_schedule_metadata_device_bytes(
2940 bindings.len(),
2941 ops.len(),
2942 waves.len(),
2943 regions.len(),
2944 generation_metadata.len(),
2945 filter_comparisons.len(),
2946 project_expressions.len(),
2947 )?;
2948 if reservation.remaining_bytes() < required_metadata_bytes {
2949 return Err(XlogError::Kernel(format!(
2950 "resident schedule metadata requires {required_metadata_bytes} reserved bytes"
2951 )));
2952 }
2953 let slots =
2954 self.upload_resident_schedule_metadata_in_reservation(&slot_descriptors, reservation)?;
2955 let op_table = self.upload_resident_schedule_metadata_in_reservation(ops, reservation)?;
2956 let wave_table =
2957 self.upload_resident_schedule_metadata_in_reservation(waves, reservation)?;
2958 let region_table =
2959 self.upload_resident_schedule_metadata_in_reservation(regions, reservation)?;
2960 let generation_table = self
2961 .upload_resident_schedule_metadata_in_reservation(&generation_metadata, reservation)?;
2962 let filter_table =
2963 self.upload_resident_schedule_metadata_in_reservation(filter_comparisons, reservation)?;
2964 let project_table = self
2965 .upload_resident_schedule_metadata_in_reservation(project_expressions, reservation)?;
2966
2967 let header_value = ResidentScheduleHeader {
2968 slots: slots.device_ptr_value(),
2969 ops: op_table.device_ptr_value(),
2970 waves: wave_table.device_ptr_value(),
2971 regions: region_table.device_ptr_value(),
2972 generation_metadata: generation_table.device_ptr_value(),
2973 filter_comparisons: filter_table.device_ptr_value(),
2974 project_expressions: project_table.device_ptr_value(),
2975 filter_mask,
2976 filter_prefix,
2977 filter_block_sums,
2978 filter_block_offsets,
2979 set_slots,
2980 set_required,
2981 join_buckets,
2982 join_next,
2983 join_required,
2984 status: external.control.status_device_ptr(),
2985 changed: external.control.changed_device_ptr(),
2986 iterations: external.control.loop_iterations_device().device_ptr_value(),
2987 scan_trace,
2988 filter_trace,
2989 semantic_scan_trace,
2990 semantic_filter_trace,
2991 schema_seen_nonempty,
2992 schema_winner_ids,
2993 receipt_table,
2994 receipt_bytes,
2995 slot_count,
2996 op_count,
2997 wave_count,
2998 region_count,
2999 filter_comparison_count,
3000 project_expression_count,
3001 filter_capacity: supplied_filter_capacity,
3002 filter_block_count,
3003 set_slot_mask,
3004 set_candidate_capacity: supplied_set_capacity,
3005 join_bucket_mask,
3006 join_right_capacity: supplied_join_capacity,
3007 schema_winner_count,
3008 receipt_count,
3009 receipt_byte_count,
3010 generation_metadata_count,
3011 abi_version: RESIDENT_SCHEDULE_ABI_VERSION,
3012 reserved: 0,
3013 };
3014 let header =
3015 self.upload_resident_schedule_metadata_in_reservation(&[header_value], reservation)?;
3016
3017 Ok(ResidentScheduleDeviceProgram {
3018 origin_provider_identity: self.provider_identity(),
3019 domain: domain.clone(),
3020 header,
3021 _slots: slots,
3022 _ops: op_table,
3023 _waves: wave_table,
3024 _regions: region_table,
3025 _generation_metadata: generation_table,
3026 _filter_comparisons: filter_table,
3027 _project_expressions: project_table,
3028 launch_config,
3029 region_descriptors: regions.to_vec(),
3030 })
3031 }
3032
3033 fn record_resident_schedule_on_stream(
3034 &self,
3035 schedule: &ResidentSchedule<'_>,
3036 region_index: u32,
3037 conditional_handle: u64,
3038 stream: &CudaStream,
3039 ) -> Result<()> {
3040 if region_index >= schedule.region_count {
3041 return Err(XlogError::Kernel(format!(
3042 "resident schedule region {region_index} is out of range"
3043 )));
3044 }
3045 let recursive = schedule.region_descriptors[region_index as usize].flags
3046 == RESIDENT_SCHEDULE_REGION_RECURSIVE;
3047 if recursive != (conditional_handle != 0) {
3048 return Err(XlogError::Kernel(
3049 "resident schedule conditional handle does not match the region kind".into(),
3050 ));
3051 }
3052 let function = self
3053 .device()
3054 .inner()
3055 .get_func(MODULE, KERNEL)
3056 .ok_or_else(|| XlogError::Kernel("resident_schedule_execute kernel missing".into()))?;
3057 let header = schedule.header.device_ptr_value();
3058 let mut params = vec![
3059 header.as_kernel_param(),
3060 region_index.as_kernel_param(),
3061 conditional_handle.as_kernel_param(),
3062 ];
3063 unsafe {
3066 function.launch_cooperative_on_stream(stream, schedule.launch_config, &mut params)
3067 }
3068 .map_err(|error| XlogError::Kernel(format!("resident schedule launch: {error}")))
3069 }
3070
3071 pub unsafe fn record_resident_schedule_region_on_stream(
3083 &self,
3084 program: &ResidentScheduleDeviceProgram,
3085 region_index: u32,
3086 conditional_body: Option<&ConditionalCudaGraphBody>,
3087 stream: &CudaStream,
3088 ) -> Result<()> {
3089 validate_execution_domain(self, &program.domain)?;
3090 if program.origin_provider_identity != self.provider_identity() {
3091 return Err(XlogError::Kernel(
3092 "resident schedule program belongs to a foreign provider".into(),
3093 ));
3094 }
3095 if !std::ptr::eq(program.domain.stream.as_ref(), stream)
3096 || program.domain.stream.cu_stream() != stream.cu_stream()
3097 || !Arc::ptr_eq(stream.context(), &program.domain.context)
3098 || stream.context().cu_ctx() != program.domain.context.cu_ctx()
3099 {
3100 return Err(XlogError::Kernel(
3101 "resident schedule record stream differs from its execution domain".into(),
3102 ));
3103 }
3104 if region_index as usize >= program.region_descriptors.len() {
3105 return Err(XlogError::Kernel(format!(
3106 "resident schedule region {region_index} is out of range"
3107 )));
3108 }
3109 let recursive = program.region_descriptors[region_index as usize].flags
3110 == RESIDENT_SCHEDULE_REGION_RECURSIVE;
3111 if recursive != conditional_body.is_some() {
3112 return Err(XlogError::Kernel(
3113 "resident schedule conditional handle does not match the region kind".into(),
3114 ));
3115 }
3116 let conditional_handle = conditional_body.map_or(0, ConditionalCudaGraphBody::handle);
3117 if conditional_body.is_some_and(|body| body.context() != program.domain.context.cu_ctx()) {
3118 return Err(XlogError::Kernel(
3119 "resident schedule conditional body belongs to a foreign CUDA context".into(),
3120 ));
3121 }
3122 let function = self
3123 .device()
3124 .inner()
3125 .get_func(MODULE, KERNEL)
3126 .ok_or_else(|| XlogError::Kernel("resident_schedule_execute kernel missing".into()))?;
3127 let header = program.header.device_ptr_value();
3128 let mut params = vec![
3129 header.as_kernel_param(),
3130 region_index.as_kernel_param(),
3131 conditional_handle.as_kernel_param(),
3132 ];
3133 function
3134 .launch_cooperative_on_stream(stream, program.launch_config, &mut params)
3135 .map_err(|error| XlogError::Kernel(format!("resident schedule launch: {error}")))
3136 }
3137
3138 pub fn capture_resident_schedule<'a>(
3140 &'a self,
3141 schedule: ResidentSchedule<'a>,
3142 region_index: u32,
3143 stream: Arc<CudaStream>,
3144 ) -> Result<ResidentScheduleGraph<'a>> {
3145 if schedule.origin_provider_identity != self.provider_identity()
3146 || schedule.origin_memory_manager != Arc::as_ptr(&self.memory) as usize
3147 {
3148 return Err(XlogError::Kernel(
3149 "resident schedule belongs to a different CUDA kernel provider".into(),
3150 ));
3151 }
3152 let provider_context = self.device.inner().stream().context();
3153 if !Arc::ptr_eq(stream.context(), provider_context)
3154 || stream.context().cu_ctx() != provider_context.cu_ctx()
3155 {
3156 return Err(XlogError::Kernel(
3157 "resident schedule stream belongs to a foreign CUDA context".into(),
3158 ));
3159 }
3160 if region_index != 0 {
3161 return Err(XlogError::Kernel(
3162 "resident schedule capture must begin with its first region".into(),
3163 ));
3164 }
3165 let graph_error =
3166 |error| XlogError::Kernel(format!("resident schedule conditional graph: {error}"));
3167 let mut builder = ConditionalCudaGraphSequenceBuilder::new(&stream).map_err(graph_error)?;
3168 for (index, region) in schedule.region_descriptors.iter().enumerate() {
3169 let region_index = checked_u32(index, "region index")?;
3170 if region.flags == RESIDENT_SCHEDULE_REGION_RECURSIVE {
3171 let initial_value = u32::from(region.iteration_limit != 0);
3172 builder
3173 .add_conditional_while(initial_value, true, |body| {
3174 let handle = body.handle();
3175 body.capture_on_stream(&stream, || {
3176 self.record_resident_schedule_on_stream(
3177 &schedule,
3178 region_index,
3179 handle,
3180 &stream,
3181 )
3182 })
3183 })
3184 .map_err(graph_error)?;
3185 } else {
3186 builder
3187 .capture_segment_on_stream(&stream, || {
3188 self.record_resident_schedule_on_stream(&schedule, region_index, 0, &stream)
3189 })
3190 .map_err(graph_error)?;
3191 }
3192 }
3193 let graph = builder.instantiate().map_err(graph_error)?;
3194 Ok(ResidentScheduleGraph {
3195 graph,
3196 schedule,
3197 provider: self,
3198 stream,
3199 in_flight: false,
3200 })
3201 }
3202
3203 fn observe_resident_schedule(
3204 &self,
3205 schedule: &mut ResidentSchedule<'_>,
3206 stream: &CudaStream,
3207 ) -> Result<ResidentScheduleReceipt> {
3208 if schedule.origin_provider_identity != self.provider_identity()
3209 || schedule.origin_memory_manager != Arc::as_ptr(&self.memory) as usize
3210 {
3211 return Err(XlogError::Kernel(
3212 "resident schedule belongs to a different CUDA kernel provider".into(),
3213 ));
3214 }
3215 let bytes = schedule
3216 .pinned_receipt
3217 .copy_from_device(schedule.receipt_bytes.device_ptr_value(), stream)?;
3218 self.record_final_observation_transfer(bytes.len() as u64);
3219 let status_bytes = std::mem::size_of::<ResidentTerminalStatus>();
3220 let expected_bytes = status_bytes
3221 .checked_add(
3222 std::mem::size_of::<u32>()
3223 .checked_mul(schedule.receipt_slots.len() + 1)
3224 .ok_or_else(|| {
3225 XlogError::Kernel("resident schedule receipt size overflow".into())
3226 })?,
3227 )
3228 .ok_or_else(|| XlogError::Kernel("resident schedule receipt size overflow".into()))?;
3229 if bytes.len() != expected_bytes {
3230 return Err(XlogError::Kernel(
3231 "resident schedule receipt has an invalid byte length".into(),
3232 ));
3233 }
3234 let status =
3236 unsafe { std::ptr::read_unaligned(bytes.as_ptr().cast::<ResidentTerminalStatus>()) };
3237 let changed = u32::from_ne_bytes(
3238 bytes[status_bytes..status_bytes + 4]
3239 .try_into()
3240 .expect("four bytes checked"),
3241 );
3242 let mut counts = Vec::new();
3243 for chunk in bytes[status_bytes + 4..].chunks_exact(4) {
3244 counts.push(u32::from_ne_bytes(
3245 chunk.try_into().expect("four-byte chunk"),
3246 ));
3247 }
3248 finalize_schedule_output_counts(
3249 self,
3250 &schedule.relations,
3251 &schedule.receipt_slots,
3252 &counts,
3253 )?;
3254 counts.truncate(schedule.requested_receipt_count);
3255 Ok(ResidentScheduleReceipt {
3256 status,
3257 changed,
3258 counts,
3259 })
3260 }
3261}
3262
3263#[cfg(test)]
3264mod tests {
3265 use std::mem::{align_of, offset_of, size_of};
3266 use std::sync::Arc;
3267 use std::time::{Duration, Instant};
3268
3269 use cudarc::driver::{CudaStream, LaunchConfig};
3270 use xlog_core::MemoryBudget;
3271
3272 use crate::cuda_compat::LaunchAsync;
3273 use crate::cuda_graph::{CapturedCudaGraph, CudaGraphNodeKind};
3274 use crate::device::CudaFunction;
3275 use crate::device_runtime::{
3276 AsyncCudaResource, DeviceMemoryResource, GlobalDeviceBudget, LoggingResource, NullSink,
3277 StreamPool, XlogDeviceRuntime,
3278 };
3279 use crate::memory::GpuMemoryManager;
3280 use crate::provider::resident_filter_project::{
3281 ResidentFilterComparison, ResidentFilterOperand, ResidentProjectExpr, ResidentScalar,
3282 };
3283 use crate::provider::resident_relational::{
3284 ResidentJoinKind, ResidentResourceCode, ResidentTerminalCode,
3285 };
3286 use crate::provider::CompareOp;
3287 use crate::{CudaBuffer, CudaColumn, CudaDevice, CudaKernelProvider, DlpackManagedTensor};
3288 use xlog_core::{ScalarType, Schema, XlogError};
3289
3290 fn cuda_test_device() -> Option<Arc<CudaDevice>> {
3291 match CudaDevice::new(0) {
3292 Ok(device) => Some(Arc::new(device)),
3293 Err(error) if std::env::var("XLOG_REQUIRE_CUDA").as_deref() == Ok("1") => {
3294 panic!("XLOG_REQUIRE_CUDA=1 but CUDA device initialization failed: {error}")
3295 }
3296 Err(error) => {
3297 eprintln!("Skipping resident schedule CUDA test: {error}");
3298 None
3299 }
3300 }
3301 }
3302
3303 fn provider() -> Option<CudaKernelProvider> {
3304 let device = cuda_test_device()?;
3305 let memory = Arc::new(GpuMemoryManager::new(
3306 Arc::clone(&device),
3307 MemoryBudget::with_limit(512 * 1024 * 1024),
3308 ));
3309 match CudaKernelProvider::new(device, memory) {
3310 Ok(provider) => Some(provider),
3311 Err(error) if std::env::var("XLOG_REQUIRE_CUDA").as_deref() == Ok("1") => {
3312 panic!("XLOG_REQUIRE_CUDA=1 but resident schedule setup failed: {error}")
3313 }
3314 Err(error) => {
3315 eprintln!("Skipping resident schedule CUDA test: {error}");
3316 None
3317 }
3318 }
3319 }
3320
3321 fn runtime_provider() -> Option<CudaKernelProvider> {
3322 let device = cuda_test_device()?;
3323 let pool = Arc::new(StreamPool::with_defaults(Arc::clone(&device)));
3324 let sink = Arc::new(NullSink::new());
3325 let async_resource: Box<dyn DeviceMemoryResource + Send + Sync> = Box::new(
3326 AsyncCudaResource::new(Arc::clone(&device), 0, Arc::clone(&pool)),
3327 );
3328 let logging: Box<dyn DeviceMemoryResource + Send + Sync> =
3329 Box::new(LoggingResource::new(async_resource, sink));
3330 let budget: Box<dyn DeviceMemoryResource + Send + Sync> =
3331 Box::new(GlobalDeviceBudget::new(logging, 512 * 1024 * 1024));
3332 let runtime = Arc::new(XlogDeviceRuntime::with_resource(
3333 Arc::clone(&device),
3334 0,
3335 pool,
3336 budget,
3337 ));
3338 let memory = Arc::new(GpuMemoryManager::with_runtime(
3339 Arc::clone(&device),
3340 MemoryBudget::with_limit(512 * 1024 * 1024),
3341 runtime,
3342 ));
3343 match CudaKernelProvider::with_runtime(device, memory) {
3344 Ok(provider) => Some(provider),
3345 Err(error) if std::env::var("XLOG_REQUIRE_CUDA").as_deref() == Ok("1") => {
3346 panic!("XLOG_REQUIRE_CUDA=1 but resident schedule runtime setup failed: {error}")
3347 }
3348 Err(error) => {
3349 eprintln!("Skipping resident schedule runtime CUDA test: {error}");
3350 None
3351 }
3352 }
3353 }
3354
3355 #[track_caller]
3356 fn schedule_kernel_error(result: xlog_core::Result<super::ResidentSchedule<'_>>) -> String {
3357 match result {
3358 Err(XlogError::Kernel(message)) => message,
3359 Err(error) => panic!("unexpected resident schedule error: {error}"),
3360 Ok(_) => panic!("malformed resident schedule unexpectedly prepared"),
3361 }
3362 }
3363
3364 fn schema(prefix: &str, types: &[ScalarType]) -> Schema {
3365 Schema::new(
3366 types
3367 .iter()
3368 .copied()
3369 .enumerate()
3370 .map(|(index, scalar)| (format!("{prefix}_{index}"), scalar))
3371 .collect(),
3372 )
3373 }
3374
3375 fn buffer(provider: &CudaKernelProvider, schema: Schema, columns: &[Vec<u64>]) -> CudaBuffer {
3376 assert_eq!(columns.len(), schema.arity());
3377 let encoded: Vec<Vec<u8>> = columns
3378 .iter()
3379 .enumerate()
3380 .map(|(column, values)| {
3381 if schema
3382 .column_type(column)
3383 .expect("column type")
3384 .size_bytes()
3385 == 4
3386 {
3387 values
3388 .iter()
3389 .flat_map(|value| (*value as u32).to_le_bytes())
3390 .collect()
3391 } else {
3392 values
3393 .iter()
3394 .flat_map(|value| value.to_le_bytes())
3395 .collect()
3396 }
3397 })
3398 .collect();
3399 let slices: Vec<&[u8]> = encoded.iter().map(Vec::as_slice).collect();
3400 provider
3401 .create_buffer_from_slices(&slices, schema)
3402 .expect("resident schedule test input")
3403 }
3404
3405 fn columns_from_rows(rows: &[Vec<u64>]) -> Vec<Vec<u64>> {
3406 let arity = rows.first().map_or(0, Vec::len);
3407 (0..arity)
3408 .map(|column| rows.iter().map(|row| row[column]).collect())
3409 .collect()
3410 }
3411
3412 fn rows_in_device_order(provider: &CudaKernelProvider, buffer: &CudaBuffer) -> Vec<Vec<u64>> {
3413 let count = provider
3414 .device_row_count(buffer)
3415 .expect("logical row count");
3416 let columns: Vec<Vec<u64>> = (0..buffer.arity())
3417 .map(|column| {
3418 if buffer
3419 .schema()
3420 .column_type(column)
3421 .expect("column type")
3422 .size_bytes()
3423 == 4
3424 {
3425 provider
3426 .download_column::<u32>(buffer, column)
3427 .expect("u32 column")
3428 .into_iter()
3429 .map(u64::from)
3430 .collect()
3431 } else {
3432 provider
3433 .download_column::<u64>(buffer, column)
3434 .expect("u64 column")
3435 }
3436 })
3437 .collect();
3438 (0..count)
3439 .map(|row| columns.iter().map(|column| column[row]).collect())
3440 .collect()
3441 }
3442
3443 fn normalized_rows(provider: &CudaKernelProvider, buffer: &CudaBuffer) -> Vec<Vec<u64>> {
3444 let mut rows = rows_in_device_order(provider, buffer);
3445 rows.sort_unstable();
3446 rows
3447 }
3448
3449 fn compact_set_rows(
3450 provider: &CudaKernelProvider,
3451 relation_schema: Schema,
3452 left_columns: &[Vec<u64>],
3453 right_columns: &[Vec<u64>],
3454 operation_kind: super::ResidentScheduleOpKind,
3455 output_capacity: u64,
3456 ) -> (super::ResidentScheduleReceipt, Vec<Vec<u64>>) {
3457 let left = buffer(provider, relation_schema.clone(), left_columns);
3458 let right = buffer(provider, relation_schema.clone(), right_columns);
3459 let mut output = provider
3460 .prepare_resident_relation(relation_schema, output_capacity)
3461 .expect("compact set output")
3462 .into_buffer();
3463 let relations = vec![
3464 super::ResidentScheduleRelation::source(&left, 1).expect("compact set left"),
3465 super::ResidentScheduleRelation::source(&right, 2).expect("compact set right"),
3466 super::ResidentScheduleRelation::output(&mut output, 3),
3467 ];
3468 let operation = super::ResidentOpDescriptor {
3469 kind: operation_kind,
3470 op_id: 980,
3471 out: 2,
3472 in0: 0,
3473 in1: 1,
3474 in0_generation: 1,
3475 in1_generation: 2,
3476 out_generation: 3,
3477 ..Default::default()
3478 };
3479 let wave = super::ResidentWaveDescriptor {
3480 op_count: 1,
3481 ..Default::default()
3482 };
3483 let region = super::ResidentRegionDescriptor {
3484 wave_count: 1,
3485 iteration_limit: 1,
3486 op_id: 981,
3487 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
3488 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
3489 slot_count: 3,
3490 ..Default::default()
3491 };
3492 let schedule = provider
3493 .prepare_resident_schedule(relations, &[operation], &[wave], &[region], &[], &[], &[2])
3494 .expect("prepare compact set schedule");
3495 let stream = provider
3496 .device()
3497 .inner()
3498 .stream()
3499 .context()
3500 .new_stream()
3501 .expect("compact set stream");
3502 let mut graph = provider
3503 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
3504 .expect("capture compact set schedule");
3505 graph.launch().expect("launch compact set schedule");
3506 let receipt = graph
3507 .synchronize_and_observe()
3508 .expect("observe compact set schedule");
3509 let rows = rows_in_device_order(
3510 provider,
3511 graph.relation(2).expect("compact set output relation"),
3512 );
3513 (receipt, rows)
3514 }
3515
3516 fn compact_nullary_set_count(
3517 provider: &CudaKernelProvider,
3518 left_present: bool,
3519 right_present: bool,
3520 operation_kind: super::ResidentScheduleOpKind,
3521 ) -> u32 {
3522 let relation_schema = Schema::new(Vec::<(String, ScalarType)>::new());
3523 let mut left_relation = provider
3524 .prepare_resident_relation(relation_schema.clone(), 1)
3525 .expect("nullary left");
3526 provider
3527 .initialize_resident_relation_count(&mut left_relation, 0)
3528 .expect("initialize nullary left");
3529 let mut left = left_relation.into_buffer();
3530 let mut right_relation = provider
3531 .prepare_resident_relation(relation_schema.clone(), 1)
3532 .expect("nullary right");
3533 provider
3534 .initialize_resident_relation_count(&mut right_relation, 0)
3535 .expect("initialize nullary right");
3536 let mut right = right_relation.into_buffer();
3537 let mut output = provider
3538 .prepare_resident_relation(relation_schema, 1)
3539 .expect("nullary set output")
3540 .into_buffer();
3541 left.set_cached_row_count_if_unset(0);
3542 right.set_cached_row_count_if_unset(0);
3543 let mut operations = Vec::new();
3544 if left_present {
3545 operations.push(super::ResidentOpDescriptor {
3546 kind: super::OP_UNIT,
3547 op_id: 984,
3548 out: 0,
3549 out_generation: 1,
3550 ..Default::default()
3551 });
3552 }
3553 if right_present {
3554 operations.push(super::ResidentOpDescriptor {
3555 kind: super::OP_UNIT,
3556 op_id: 985,
3557 out: 1,
3558 out_generation: 2,
3559 ..Default::default()
3560 });
3561 }
3562 operations.push(super::ResidentOpDescriptor {
3563 kind: operation_kind,
3564 op_id: 986,
3565 out: 2,
3566 in0: 0,
3567 in1: 1,
3568 in0_generation: 1,
3569 in1_generation: 2,
3570 out_generation: 3,
3571 ..Default::default()
3572 });
3573 let relations = vec![
3574 if left_present {
3575 super::ResidentScheduleRelation::output(&mut left, 1)
3576 } else {
3577 super::ResidentScheduleRelation::source(&left, 1).expect("empty nullary left")
3578 },
3579 if right_present {
3580 super::ResidentScheduleRelation::output(&mut right, 2)
3581 } else {
3582 super::ResidentScheduleRelation::source(&right, 2).expect("empty nullary right")
3583 },
3584 super::ResidentScheduleRelation::output(&mut output, 3),
3585 ];
3586 let wave = super::ResidentWaveDescriptor {
3587 op_count: operations.len() as u32,
3588 ..Default::default()
3589 };
3590 let region = super::ResidentRegionDescriptor {
3591 wave_count: 1,
3592 iteration_limit: 1,
3593 op_id: 987,
3594 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
3595 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
3596 slot_count: 3,
3597 ..Default::default()
3598 };
3599 let schedule = provider
3600 .prepare_resident_schedule(relations, &operations, &[wave], &[region], &[], &[], &[2])
3601 .expect("prepare nullary compact set schedule");
3602 let stream = provider
3603 .device()
3604 .inner()
3605 .stream()
3606 .context()
3607 .new_stream()
3608 .expect("nullary compact set stream");
3609 let mut graph = provider
3610 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
3611 .expect("capture nullary compact set schedule");
3612 graph.launch().expect("launch nullary compact set schedule");
3613 let receipt = graph
3614 .synchronize_and_observe()
3615 .expect("observe nullary compact set schedule");
3616 assert_eq!(
3617 receipt.status.code,
3618 ResidentTerminalCode::Success as u32,
3619 "nullary set terminal status: {:?}",
3620 receipt.status
3621 );
3622 receipt.counts[0]
3623 }
3624
3625 fn passthrough_schedule<'a>(
3626 provider: &CudaKernelProvider,
3627 input: &'a CudaBuffer,
3628 output: &'a mut CudaBuffer,
3629 op_id: u32,
3630 ) -> super::ResidentSchedule<'a> {
3631 let relations = vec![
3632 super::ResidentScheduleRelation::source(input, 1).expect("passthrough source"),
3633 super::ResidentScheduleRelation::output(output, 2),
3634 ];
3635 let operation = super::ResidentOpDescriptor {
3636 kind: super::OP_FILTER,
3637 op_id,
3638 out: 1,
3639 in0: 0,
3640 in0_generation: 1,
3641 out_generation: 2,
3642 ..Default::default()
3643 };
3644 let wave = super::ResidentWaveDescriptor {
3645 op_count: 1,
3646 ..Default::default()
3647 };
3648 let region = super::ResidentRegionDescriptor {
3649 wave_count: 1,
3650 iteration_limit: 1,
3651 op_id: op_id + 1,
3652 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
3653 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
3654 slot_count: 2,
3655 ..Default::default()
3656 };
3657 provider
3658 .prepare_resident_schedule(relations, &[operation], &[wave], &[region], &[], &[], &[1])
3659 .expect("prepare passthrough schedule")
3660 }
3661
3662 fn run_single_recursive_diff(
3663 provider: &CudaKernelProvider,
3664 left_values: &[u64],
3665 right_values: &[u64],
3666 iteration_limit: u32,
3667 ) -> (super::ResidentScheduleReceipt, Vec<CudaGraphNodeKind>) {
3668 let relation_schema = schema("single_recursive", &[ScalarType::U32]);
3669 let left = buffer(provider, relation_schema.clone(), &[left_values.to_vec()]);
3670 let right = buffer(provider, relation_schema.clone(), &[right_values.to_vec()]);
3671 let mut novelty = provider
3672 .prepare_resident_relation(relation_schema, left_values.len().max(1) as u64)
3673 .expect("single recursive novelty output")
3674 .into_buffer();
3675 let relations = vec![
3676 super::ResidentScheduleRelation::source(&left, 1).expect("left source"),
3677 super::ResidentScheduleRelation::source(&right, 2).expect("right source"),
3678 super::ResidentScheduleRelation::output(&mut novelty, 3),
3679 ];
3680 let operation = super::ResidentOpDescriptor {
3681 kind: super::OP_DIFF,
3682 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY,
3683 op_id: 601,
3684 out: 2,
3685 in0: 0,
3686 in1: 1,
3687 in0_generation: 1,
3688 in1_generation: 2,
3689 out_generation: 3,
3690 ..Default::default()
3691 };
3692 let wave = super::ResidentWaveDescriptor {
3693 first_op: 0,
3694 op_count: 1,
3695 ..Default::default()
3696 };
3697 let regions = [
3698 super::ResidentRegionDescriptor {
3699 first_wave: 0,
3700 wave_count: 0,
3701 iteration_limit,
3702 op_id: 600,
3703 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
3704 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
3705 first_slot: 0,
3706 slot_count: 3,
3707 generation_offset: 0,
3708 },
3709 super::ResidentRegionDescriptor {
3710 first_wave: 0,
3711 wave_count: 1,
3712 iteration_limit,
3713 op_id: 600,
3714 flags: super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
3715 first_slot: 0,
3716 slot_count: 3,
3717 generation_offset: 0,
3718 },
3719 super::ResidentRegionDescriptor {
3720 first_wave: 1,
3721 wave_count: 0,
3722 iteration_limit: 1,
3723 op_id: 602,
3724 flags: super::RESIDENT_SCHEDULE_REGION_FINALIZE,
3725 first_slot: 0,
3726 slot_count: 3,
3727 generation_offset: 0,
3728 },
3729 ];
3730 let schedule = provider
3731 .prepare_resident_schedule(relations, &[operation], &[wave], ®ions, &[], &[], &[2])
3732 .expect("prepare single recursive schedule");
3733 let stream = provider
3734 .device()
3735 .inner()
3736 .stream()
3737 .context()
3738 .new_stream()
3739 .expect("single recursive stream");
3740 let mut graph = provider
3741 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
3742 .expect("capture single recursive schedule");
3743 let kinds = graph
3744 .nodes()
3745 .expect("single recursive inventory")
3746 .into_iter()
3747 .map(|node| node.kind)
3748 .collect();
3749 graph.launch().expect("launch single recursive schedule");
3750 let receipt = graph
3751 .synchronize_and_observe()
3752 .expect("single recursive receipt");
3753 (receipt, kinds)
3754 }
3755
3756 fn run_two_scc_diff(
3757 provider: &CudaKernelProvider,
3758 first_limit: u32,
3759 second_limit: u32,
3760 first_changes: bool,
3761 second_changes: bool,
3762 ) -> (
3763 super::ResidentScheduleReceipt,
3764 Vec<CudaGraphNodeKind>,
3765 [u32; 2],
3766 ) {
3767 let relation_schema = schema("serial_recursive", &[ScalarType::U32]);
3768 let first_left = buffer(provider, relation_schema.clone(), &[vec![11]]);
3769 let first_right = buffer(
3770 provider,
3771 relation_schema.clone(),
3772 &[if first_changes { Vec::new() } else { vec![11] }],
3773 );
3774 let second_left = buffer(provider, relation_schema.clone(), &[vec![22]]);
3775 let second_right = buffer(
3776 provider,
3777 relation_schema.clone(),
3778 &[if second_changes { Vec::new() } else { vec![22] }],
3779 );
3780 let mut first_novelty = buffer(provider, relation_schema.clone(), &[vec![0x1111_1111]]);
3781 let mut second_novelty = buffer(provider, relation_schema, &[vec![0x2222_2222]]);
3782 let relations = vec![
3783 super::ResidentScheduleRelation::source(&first_left, 1).expect("first left"),
3784 super::ResidentScheduleRelation::source(&first_right, 2).expect("first right"),
3785 super::ResidentScheduleRelation::source(&second_left, 3).expect("second left"),
3786 super::ResidentScheduleRelation::source(&second_right, 4).expect("second right"),
3787 super::ResidentScheduleRelation::output(&mut first_novelty, 5),
3788 super::ResidentScheduleRelation::output(&mut second_novelty, 6),
3789 ];
3790 let operations = [
3791 super::ResidentOpDescriptor {
3792 kind: super::OP_DIFF,
3793 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY,
3794 op_id: 711,
3795 out: 4,
3796 in0: 0,
3797 in1: 1,
3798 in0_generation: 1,
3799 in1_generation: 2,
3800 out_generation: 5,
3801 ..Default::default()
3802 },
3803 super::ResidentOpDescriptor {
3804 kind: super::OP_DIFF,
3805 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY,
3806 op_id: 712,
3807 out: 5,
3808 in0: 2,
3809 in1: 3,
3810 in0_generation: 3,
3811 in1_generation: 4,
3812 out_generation: 6,
3813 ..Default::default()
3814 },
3815 ];
3816 let waves = [
3817 super::ResidentWaveDescriptor {
3818 first_op: 0,
3819 op_count: 1,
3820 ..Default::default()
3821 },
3822 super::ResidentWaveDescriptor {
3823 first_op: 1,
3824 op_count: 1,
3825 ..Default::default()
3826 },
3827 ];
3828 let regions = [
3829 super::ResidentRegionDescriptor {
3830 first_wave: 0,
3831 wave_count: 0,
3832 iteration_limit: first_limit,
3833 op_id: 701,
3834 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
3835 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
3836 first_slot: 0,
3837 slot_count: 6,
3838 generation_offset: 0,
3839 },
3840 super::ResidentRegionDescriptor {
3841 first_wave: 0,
3842 wave_count: 1,
3843 iteration_limit: first_limit,
3844 op_id: 701,
3845 flags: super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
3846 first_slot: 0,
3847 slot_count: 6,
3848 generation_offset: 0,
3849 },
3850 super::ResidentRegionDescriptor {
3851 first_wave: 1,
3852 wave_count: 0,
3853 iteration_limit: second_limit,
3854 op_id: 702,
3855 flags: super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
3856 first_slot: 0,
3857 slot_count: 6,
3858 generation_offset: 0,
3859 },
3860 super::ResidentRegionDescriptor {
3861 first_wave: 1,
3862 wave_count: 1,
3863 iteration_limit: second_limit,
3864 op_id: 702,
3865 flags: super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
3866 first_slot: 0,
3867 slot_count: 6,
3868 generation_offset: 0,
3869 },
3870 super::ResidentRegionDescriptor {
3871 first_wave: 2,
3872 wave_count: 0,
3873 iteration_limit: 1,
3874 op_id: 703,
3875 flags: super::RESIDENT_SCHEDULE_REGION_FINALIZE,
3876 first_slot: 0,
3877 slot_count: 6,
3878 generation_offset: 0,
3879 },
3880 ];
3881 let schedule = provider
3882 .prepare_resident_schedule(relations, &operations, &waves, ®ions, &[], &[], &[4, 5])
3883 .expect("prepare serial recursive schedule");
3884 let stream = provider
3885 .device()
3886 .inner()
3887 .stream()
3888 .context()
3889 .new_stream()
3890 .expect("serial recursive stream");
3891 let mut graph = provider
3892 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
3893 .expect("capture serial recursive schedule");
3894 let kinds = graph
3895 .nodes()
3896 .expect("serial recursive inventory")
3897 .into_iter()
3898 .map(|node| node.kind)
3899 .collect();
3900 graph.launch().expect("launch serial recursive schedule");
3901 let receipt = graph
3902 .synchronize_and_observe()
3903 .expect("serial recursive receipt");
3904 let stored = [4_usize, 5].map(|slot| {
3905 let bytes: Vec<u8> = provider
3906 .device()
3907 .inner()
3908 .dtoh_sync_copy(
3909 graph
3910 .relation(slot)
3911 .expect("novelty relation")
3912 .column(0)
3913 .expect("novelty column"),
3914 )
3915 .expect("novelty storage");
3916 u32::from_le_bytes(bytes[..4].try_into().expect("u32 storage"))
3917 });
3918 (receipt, kinds, stored)
3919 }
3920
3921 #[allow(dead_code)]
3922 unsafe fn selected_stream_cooperative_launch_is_available(
3923 function: CudaFunction,
3924 stream: &CudaStream,
3925 config: LaunchConfig,
3926 params: &mut Vec<*mut std::ffi::c_void>,
3927 ) {
3928 function
3929 .launch_cooperative_on_stream(stream, config, params)
3930 .expect("selected-stream cooperative launch");
3931 }
3932
3933 #[test]
3934 fn schedule_wire_abi_has_exact_sizes_alignments_and_offsets() {
3935 assert_eq!(size_of::<super::ResidentScheduleOpKind>(), 4);
3936 assert_eq!(align_of::<super::ResidentScheduleOpKind>(), 4);
3937 assert_eq!(size_of::<super::ResidentRelationView>(), 224);
3938 assert_eq!(align_of::<super::ResidentRelationView>(), 8);
3939 assert_eq!(offset_of!(super::ResidentRelationView, widths), 136);
3940 assert_eq!(offset_of!(super::ResidentRelationView, num_rows), 216);
3941
3942 assert_eq!(size_of::<super::ResidentRelationSlot>(), 240);
3943 assert_eq!(align_of::<super::ResidentRelationSlot>(), 16);
3944 assert_eq!(offset_of!(super::ResidentRelationSlot, generation), 224);
3945 assert_eq!(offset_of!(super::ResidentRelationSlot, schema_tag), 236);
3946
3947 assert_eq!(size_of::<super::ResidentOpDescriptor>(), 72);
3948 assert_eq!(align_of::<super::ResidentOpDescriptor>(), 4);
3949 assert_eq!(offset_of!(super::ResidentOpDescriptor, aux_offset), 36);
3950 assert_eq!(
3951 offset_of!(super::ResidentOpDescriptor, schema_winner_head),
3952 60
3953 );
3954 assert_eq!(
3955 offset_of!(super::ResidentOpDescriptor, schema_winner_id),
3956 64
3957 );
3958 assert_eq!(offset_of!(super::ResidentOpDescriptor, reserved), 68);
3959
3960 assert_eq!(size_of::<super::ResidentWaveDescriptor>(), 16);
3961 assert_eq!(align_of::<super::ResidentWaveDescriptor>(), 4);
3962 assert_eq!(offset_of!(super::ResidentWaveDescriptor, op_count), 4);
3963
3964 assert_eq!(size_of::<super::ResidentRegionDescriptor>(), 32);
3965 assert_eq!(align_of::<super::ResidentRegionDescriptor>(), 4);
3966 assert_eq!(offset_of!(super::ResidentRegionDescriptor, slot_count), 24);
3967 assert_eq!(
3968 offset_of!(super::ResidentRegionDescriptor, generation_offset),
3969 28
3970 );
3971
3972 assert_eq!(size_of::<super::ResidentScheduleHeader>(), 288);
3973 assert_eq!(align_of::<super::ResidentScheduleHeader>(), 16);
3974 assert_eq!(offset_of!(super::ResidentScheduleHeader, slots), 0);
3975 assert_eq!(
3976 offset_of!(super::ResidentScheduleHeader, receipt_bytes),
3977 208
3978 );
3979 assert_eq!(
3980 offset_of!(super::ResidentScheduleHeader, generation_metadata),
3981 32
3982 );
3983 assert_eq!(
3984 offset_of!(super::ResidentScheduleHeader, schema_seen_nonempty),
3985 184
3986 );
3987 assert_eq!(
3988 offset_of!(super::ResidentScheduleHeader, schema_winner_ids),
3989 192
3990 );
3991 assert_eq!(
3992 offset_of!(super::ResidentScheduleHeader, semantic_scan_trace),
3993 168
3994 );
3995 assert_eq!(
3996 offset_of!(super::ResidentScheduleHeader, semantic_filter_trace),
3997 176
3998 );
3999 assert_eq!(offset_of!(super::ResidentScheduleHeader, slot_count), 216);
4000 assert_eq!(
4001 offset_of!(super::ResidentScheduleHeader, generation_metadata_count),
4002 276
4003 );
4004 assert_eq!(
4005 offset_of!(super::ResidentScheduleHeader, schema_winner_count),
4006 264
4007 );
4008 assert_eq!(offset_of!(super::ResidentScheduleHeader, abi_version), 280);
4009 assert_eq!(offset_of!(super::ResidentScheduleHeader, reserved), 284);
4010 assert_eq!(super::RESIDENT_SCHEDULE_ABI_VERSION, 3);
4011 }
4012
4013 #[test]
4014 fn cuda_schedule_wire_matches_active_host_abi() {
4015 let source = include_str!("../../kernels/resident_schedule.cu");
4016 for required in [
4017 "constexpr uint32_t kAbiVersion = 3;",
4018 "uint32_t schema_winner_head;",
4019 "uint32_t schema_winner_id;",
4020 "uint32_t generation_offset;",
4021 "uint64_t generation_metadata;",
4022 "uint64_t schema_seen_nonempty;",
4023 "uint64_t schema_winner_ids;",
4024 "uint64_t semantic_scan_trace;",
4025 "uint64_t semantic_filter_trace;",
4026 "uint32_t generation_metadata_count;",
4027 "uint32_t abi_version;",
4028 "static_assert(sizeof(ResidentOpDescriptor) == 72",
4029 "static_assert(sizeof(ResidentScheduleHeader) == 288",
4030 ] {
4031 assert!(
4032 source.contains(required),
4033 "missing CUDA ABI fragment: {required}"
4034 );
4035 }
4036 }
4037
4038 #[test]
4039 fn flattened_descriptor_tables_have_exact_host_and_cuda_layouts() {
4040 use std::mem::{align_of, offset_of, size_of};
4041
4042 assert_eq!(size_of::<super::ResidentFilterComparisonDescriptor>(), 48);
4043 assert_eq!(align_of::<super::ResidentFilterComparisonDescriptor>(), 8);
4044 assert_eq!(
4045 offset_of!(super::ResidentFilterComparisonDescriptor, left_constant),
4046 32
4047 );
4048 assert_eq!(
4049 offset_of!(super::ResidentFilterComparisonDescriptor, right_constant),
4050 40
4051 );
4052 assert_eq!(size_of::<super::ResidentProjectExpressionDescriptor>(), 24);
4053 assert_eq!(align_of::<super::ResidentProjectExpressionDescriptor>(), 8);
4054 assert_eq!(
4055 offset_of!(super::ResidentProjectExpressionDescriptor, constant),
4056 16
4057 );
4058
4059 let cuda = include_str!("../../kernels/resident_schedule.cu");
4060 for required in [
4061 "static_assert(sizeof(ResidentFilterComparisonDescriptor) == 48",
4062 "static_assert(offsetof(ResidentFilterComparisonDescriptor, left_constant) == 32",
4063 "static_assert(offsetof(ResidentFilterComparisonDescriptor, right_constant) == 40",
4064 "static_assert(sizeof(ResidentProjectExpressionDescriptor) == 24",
4065 "static_assert(offsetof(ResidentProjectExpressionDescriptor, constant) == 16",
4066 ] {
4067 assert!(
4068 cuda.contains(required),
4069 "missing CUDA layout assertion: {required}"
4070 );
4071 }
4072 }
4073
4074 #[test]
4075 fn flattened_filter_and_project_descriptors_are_fully_validated() {
4076 let slot = |widths: &[u32], schema_tag| {
4077 let mut relation = super::ResidentRelationView::default();
4078 relation.arity = widths.len() as u32;
4079 relation.capacity = 4;
4080 relation.widths[..widths.len()].copy_from_slice(widths);
4081 super::ResidentRelationSlot {
4082 relation,
4083 schema_tag,
4084 ..Default::default()
4085 }
4086 };
4087 let slots = [slot(&[4, 4], 1), slot(&[4, 4], 1), slot(&[4, 8], 2)];
4088 let slot_types = [
4089 vec![ScalarType::Symbol, ScalarType::Symbol],
4090 vec![ScalarType::Symbol, ScalarType::Symbol],
4091 vec![ScalarType::Symbol, ScalarType::U64],
4092 ];
4093 let filter = super::ResidentOpDescriptor {
4094 kind: super::ResidentScheduleOpKind::Filter,
4095 out: 1,
4096 in0: 0,
4097 aux_count: 1,
4098 ..Default::default()
4099 };
4100 let project = super::ResidentOpDescriptor {
4101 kind: super::ResidentScheduleOpKind::Project,
4102 out: 2,
4103 in0: 0,
4104 aux_count: 2,
4105 ..Default::default()
4106 };
4107 let comparison = super::ResidentFilterComparisonDescriptor {
4108 left_kind: 0,
4109 left_column: 0,
4110 right_kind: 1,
4111 op: 0,
4112 width: 4,
4113 right_constant: 7,
4114 ..Default::default()
4115 };
4116 let expressions = [
4117 super::ResidentProjectExpressionDescriptor::column(0, 4),
4118 super::ResidentProjectExpressionDescriptor::constant(8, 9),
4119 ];
4120 super::validate_flattened_filter_project_descriptors(
4121 &slots,
4122 &slot_types,
4123 &[filter, project],
4124 &[comparison],
4125 &expressions,
4126 )
4127 .expect("valid flattened descriptors");
4128
4129 let invalid_comparisons = [
4130 super::ResidentFilterComparisonDescriptor {
4131 left_kind: 2,
4132 ..comparison
4133 },
4134 super::ResidentFilterComparisonDescriptor {
4135 left_column: 2,
4136 ..comparison
4137 },
4138 super::ResidentFilterComparisonDescriptor {
4139 width: 8,
4140 ..comparison
4141 },
4142 super::ResidentFilterComparisonDescriptor {
4143 op: 6,
4144 ..comparison
4145 },
4146 super::ResidentFilterComparisonDescriptor {
4147 reserved_zero: 1,
4148 ..comparison
4149 },
4150 ];
4151 for invalid in invalid_comparisons {
4152 assert!(super::validate_flattened_filter_project_descriptors(
4153 &slots,
4154 &slot_types,
4155 &[filter],
4156 &[invalid],
4157 &[],
4158 )
4159 .is_err());
4160 }
4161
4162 let mismatched_types = [
4163 vec![ScalarType::Symbol, ScalarType::U32],
4164 slot_types[1].clone(),
4165 slot_types[2].clone(),
4166 ];
4167 let two_columns = super::ResidentFilterComparisonDescriptor {
4168 right_kind: 0,
4169 right_column: 1,
4170 ..comparison
4171 };
4172 assert!(super::validate_flattened_filter_project_descriptors(
4173 &slots,
4174 &mismatched_types,
4175 &[filter],
4176 &[two_columns],
4177 &[],
4178 )
4179 .is_err());
4180
4181 for invalid in [
4182 super::ResidentProjectExpressionDescriptor {
4183 kind: 2,
4184 ..expressions[0]
4185 },
4186 super::ResidentProjectExpressionDescriptor {
4187 column: 2,
4188 ..expressions[0]
4189 },
4190 super::ResidentProjectExpressionDescriptor {
4191 width: 8,
4192 ..expressions[0]
4193 },
4194 super::ResidentProjectExpressionDescriptor {
4195 reserved: 1,
4196 ..expressions[0]
4197 },
4198 ] {
4199 assert!(super::validate_flattened_filter_project_descriptors(
4200 &slots,
4201 &slot_types,
4202 &[project],
4203 &[],
4204 &[invalid, expressions[1]],
4205 )
4206 .is_err());
4207 }
4208 assert!(super::validate_flattened_filter_project_descriptors(
4209 &slots,
4210 &slot_types,
4211 &[super::ResidentOpDescriptor {
4212 aux_offset: u32::MAX,
4213 aux_count: 1,
4214 ..filter
4215 }],
4216 &[comparison],
4217 &[],
4218 )
4219 .is_err());
4220 }
4221
4222 #[test]
4223 fn shared_validator_rejects_writing_alias_and_accepts_scan_identity() {
4224 let mut relation = super::ResidentRelationView::default();
4225 relation.arity = 1;
4226 relation.capacity = 4;
4227 relation.widths[0] = 4;
4228 let slots = [super::ResidentRelationSlot {
4229 relation,
4230 generation: 7,
4231 flags: super::RESIDENT_SCHEDULE_SLOT_PERMANENT | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
4232 schema_tag: 1,
4233 ..Default::default()
4234 }];
4235 let types = [vec![ScalarType::U32]];
4236 let waves = [super::ResidentWaveDescriptor {
4237 first_op: 0,
4238 op_count: 1,
4239 ..Default::default()
4240 }];
4241 let regions = [super::ResidentRegionDescriptor {
4242 first_wave: 0,
4243 wave_count: 1,
4244 iteration_limit: 1,
4245 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4246 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4247 first_slot: 0,
4248 slot_count: 1,
4249 generation_offset: 0,
4250 ..Default::default()
4251 }];
4252 let aliased_filter = [super::ResidentOpDescriptor {
4253 kind: super::ResidentScheduleOpKind::Filter,
4254 op_id: 41,
4255 out: 0,
4256 in0: 0,
4257 in0_generation: 7,
4258 out_generation: 7,
4259 ..Default::default()
4260 }];
4261 assert!(super::validate_schedule_program(
4262 &slots,
4263 &types,
4264 &aliased_filter,
4265 &waves,
4266 ®ions,
4267 &[7],
4268 &[],
4269 &[],
4270 &[],
4271 )
4272 .is_err());
4273
4274 let scan = [super::ResidentOpDescriptor::scan(42, 0, 7)];
4275 super::validate_schedule_program(
4276 &slots,
4277 &types,
4278 &scan,
4279 &waves,
4280 ®ions,
4281 &[7],
4282 &[],
4283 &[],
4284 &[],
4285 )
4286 .expect("scan is the read-only same-slot exception");
4287 }
4288
4289 #[test]
4290 fn shared_validator_rejects_writes_to_immutable_source_slots() {
4291 let mut relation = super::ResidentRelationView::default();
4292 relation.capacity = 1;
4293 let slots = [super::ResidentRelationSlot {
4294 relation,
4295 generation: 3,
4296 flags: super::RESIDENT_SCHEDULE_SLOT_SOURCE | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
4297 ..Default::default()
4298 }];
4299 let op = [super::ResidentOpDescriptor {
4300 kind: super::ResidentScheduleOpKind::Unit,
4301 out: 0,
4302 out_generation: 3,
4303 ..Default::default()
4304 }];
4305 let waves = [super::ResidentWaveDescriptor {
4306 first_op: 0,
4307 op_count: 1,
4308 ..Default::default()
4309 }];
4310 let regions = [super::ResidentRegionDescriptor {
4311 first_wave: 0,
4312 wave_count: 1,
4313 iteration_limit: 1,
4314 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4315 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4316 first_slot: 0,
4317 slot_count: 1,
4318 generation_offset: 0,
4319 ..Default::default()
4320 }];
4321
4322 assert!(super::validate_schedule_program(
4323 &slots,
4324 &[Vec::new()],
4325 &op,
4326 &waves,
4327 ®ions,
4328 &[3],
4329 &[],
4330 &[],
4331 &[],
4332 )
4333 .is_err());
4334 }
4335
4336 #[test]
4337 fn shared_validator_rejects_unknown_relation_slot_flags() {
4338 let mut relation = super::ResidentRelationView::default();
4339 relation.arity = 1;
4340 relation.capacity = 1;
4341 relation.widths[0] = 4;
4342 let slots = [super::ResidentRelationSlot {
4343 relation,
4344 generation: 3,
4345 flags: super::RESIDENT_SCHEDULE_SLOT_PERMANENT
4346 | super::RESIDENT_SCHEDULE_SLOT_DEFINED
4347 | 8,
4348 ..Default::default()
4349 }];
4350 let ops = [super::ResidentOpDescriptor::scan(43, 0, 3)];
4351 let waves = [super::ResidentWaveDescriptor {
4352 first_op: 0,
4353 op_count: 1,
4354 ..Default::default()
4355 }];
4356 let regions = [super::ResidentRegionDescriptor {
4357 first_wave: 0,
4358 wave_count: 1,
4359 iteration_limit: 1,
4360 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4361 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4362 first_slot: 0,
4363 slot_count: 1,
4364 generation_offset: 0,
4365 ..Default::default()
4366 }];
4367
4368 assert!(super::validate_schedule_program(
4369 &slots,
4370 &[vec![ScalarType::U32]],
4371 &ops,
4372 &waves,
4373 ®ions,
4374 &[3],
4375 &[],
4376 &[],
4377 &[],
4378 )
4379 .is_err());
4380 }
4381
4382 #[test]
4383 fn shared_validator_simulates_scratch_definedness_and_generation_transitions() {
4384 let mut relation = super::ResidentRelationView::default();
4385 relation.capacity = 1;
4386 let slots = [super::ResidentRelationSlot {
4387 relation,
4388 generation: 4,
4389 ..Default::default()
4390 }];
4391 let types = [Vec::new()];
4392 let region = super::ResidentRegionDescriptor {
4393 first_wave: 0,
4394 wave_count: 1,
4395 iteration_limit: 1,
4396 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4397 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4398 first_slot: 0,
4399 slot_count: 1,
4400 generation_offset: 0,
4401 ..Default::default()
4402 };
4403
4404 let scan_before_definition = [super::ResidentOpDescriptor::scan(51, 0, 4)];
4405 let one_op_wave = [super::ResidentWaveDescriptor {
4406 first_op: 0,
4407 op_count: 1,
4408 ..Default::default()
4409 }];
4410 assert!(super::validate_schedule_program(
4411 &slots,
4412 &types,
4413 &scan_before_definition,
4414 &one_op_wave,
4415 &[region],
4416 &[4],
4417 &[],
4418 &[],
4419 &[],
4420 )
4421 .is_err());
4422
4423 let producer_then_scan = [
4424 super::ResidentOpDescriptor::unit(52, 0, 5),
4425 super::ResidentOpDescriptor::scan(53, 0, 5),
4426 ];
4427 let two_op_wave = [super::ResidentWaveDescriptor {
4428 first_op: 0,
4429 op_count: 2,
4430 ..Default::default()
4431 }];
4432 super::validate_schedule_program(
4433 &slots,
4434 &types,
4435 &producer_then_scan,
4436 &two_op_wave,
4437 &[region],
4438 &[4],
4439 &[],
4440 &[],
4441 &[],
4442 )
4443 .expect("successful producer defines its next-generation scratch output");
4444
4445 let skipped_generation = [super::ResidentOpDescriptor::unit(54, 0, 6)];
4446 assert!(super::validate_schedule_program(
4447 &slots,
4448 &types,
4449 &skipped_generation,
4450 &one_op_wave,
4451 &[region],
4452 &[4],
4453 &[],
4454 &[],
4455 &[],
4456 )
4457 .is_err());
4458 }
4459
4460 #[test]
4461 fn shared_validator_rejects_physical_slots_outside_their_region_scope() {
4462 let mut relation = super::ResidentRelationView::default();
4463 relation.arity = 1;
4464 relation.capacity = 1;
4465 relation.widths[0] = 4;
4466 let slots = [
4467 super::ResidentRelationSlot {
4468 relation,
4469 generation: 1,
4470 flags: super::RESIDENT_SCHEDULE_SLOT_PERMANENT
4471 | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
4472 schema_tag: 1,
4473 ..Default::default()
4474 },
4475 super::ResidentRelationSlot {
4476 relation,
4477 generation: 2,
4478 flags: super::RESIDENT_SCHEDULE_SLOT_PERMANENT
4479 | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
4480 schema_tag: 1,
4481 ..Default::default()
4482 },
4483 ];
4484 let types = [vec![ScalarType::U32], vec![ScalarType::U32]];
4485 let ops = [super::ResidentOpDescriptor::scan(61, 1, 2)];
4486 let waves = [super::ResidentWaveDescriptor {
4487 first_op: 0,
4488 op_count: 1,
4489 ..Default::default()
4490 }];
4491 let regions = [
4492 super::ResidentRegionDescriptor {
4493 first_wave: 0,
4494 wave_count: 0,
4495 iteration_limit: 1,
4496 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE,
4497 first_slot: 0,
4498 slot_count: 2,
4499 generation_offset: 0,
4500 ..Default::default()
4501 },
4502 super::ResidentRegionDescriptor {
4503 first_wave: 0,
4504 wave_count: 1,
4505 iteration_limit: 1,
4506 flags: super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4507 first_slot: 0,
4508 slot_count: 1,
4509 generation_offset: 2,
4510 ..Default::default()
4511 },
4512 ];
4513
4514 assert!(super::validate_schedule_program(
4515 &slots,
4516 &types,
4517 &ops,
4518 &waves,
4519 ®ions,
4520 &[1, 2, 1],
4521 &[],
4522 &[],
4523 &[],
4524 )
4525 .is_err());
4526 }
4527
4528 #[test]
4529 fn shared_validator_requires_recursive_novelty_and_accepts_final_delta_copy_marker() {
4530 let mut relation = super::ResidentRelationView::default();
4531 relation.arity = 1;
4532 relation.capacity = 1;
4533 relation.widths[0] = 4;
4534 let slots = [
4535 super::ResidentRelationSlot {
4536 relation,
4537 generation: 1,
4538 flags: super::RESIDENT_SCHEDULE_SLOT_PERMANENT
4539 | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
4540 schema_tag: 1,
4541 ..Default::default()
4542 },
4543 super::ResidentRelationSlot {
4544 relation,
4545 generation: 2,
4546 flags: super::RESIDENT_SCHEDULE_SLOT_PERMANENT
4547 | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
4548 schema_tag: 1,
4549 ..Default::default()
4550 },
4551 super::ResidentRelationSlot {
4552 relation,
4553 generation: 3,
4554 schema_tag: 1,
4555 ..Default::default()
4556 },
4557 ];
4558 let types = [
4559 vec![ScalarType::U32],
4560 vec![ScalarType::U32],
4561 vec![ScalarType::U32],
4562 ];
4563 let waves = [super::ResidentWaveDescriptor {
4564 first_op: 0,
4565 op_count: 1,
4566 ..Default::default()
4567 }];
4568 let regions = [
4569 super::ResidentRegionDescriptor {
4570 first_wave: 0,
4571 wave_count: 0,
4572 iteration_limit: 5,
4573 op_id: 70,
4574 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4575 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
4576 first_slot: 0,
4577 slot_count: 3,
4578 generation_offset: 0,
4579 },
4580 super::ResidentRegionDescriptor {
4581 first_wave: 0,
4582 wave_count: 1,
4583 iteration_limit: 5,
4584 op_id: 70,
4585 flags: super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
4586 first_slot: 0,
4587 slot_count: 3,
4588 generation_offset: 3,
4589 },
4590 super::ResidentRegionDescriptor {
4591 first_wave: 1,
4592 wave_count: 0,
4593 iteration_limit: 1,
4594 flags: super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4595 first_slot: 0,
4596 slot_count: 3,
4597 generation_offset: 6,
4598 ..Default::default()
4599 },
4600 ];
4601 let baselines = [1, 2, 3, 1, 2, 3, 1, 2, 3];
4602 let diff = super::ResidentOpDescriptor {
4603 kind: super::ResidentScheduleOpKind::Diff,
4604 op_id: 71,
4605 out: 2,
4606 in0: 0,
4607 in1: 1,
4608 in0_generation: 1,
4609 in1_generation: 2,
4610 out_generation: 3,
4611 ..Default::default()
4612 };
4613 assert!(super::validate_schedule_program(
4614 &slots,
4615 &types,
4616 &[diff],
4617 &waves,
4618 ®ions,
4619 &baselines,
4620 &[],
4621 &[],
4622 &[],
4623 )
4624 .is_err());
4625
4626 let marked = super::ResidentOpDescriptor {
4627 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY
4628 | super::RESIDENT_SCHEDULE_OP_MARK_SCHEMA_WINNER,
4629 schema_winner_head: 0,
4630 schema_winner_id: 99,
4631 ..diff
4632 };
4633 super::validate_schedule_program(
4634 &slots,
4635 &types,
4636 &[marked],
4637 &waves,
4638 ®ions,
4639 &baselines,
4640 &[],
4641 &[],
4642 &[99],
4643 )
4644 .expect("recursive Diff may mark novelty and a schema candidate together");
4645
4646 let delta_copy = super::ResidentOpDescriptor {
4647 kind: super::ResidentScheduleOpKind::Project,
4648 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY,
4649 op_id: 72,
4650 out: 2,
4651 in0: 0,
4652 in0_generation: 1,
4653 out_generation: 3,
4654 aux_count: 1,
4655 ..Default::default()
4656 };
4657 super::validate_schedule_program(
4658 &slots,
4659 &types,
4660 &[delta_copy],
4661 &waves,
4662 ®ions,
4663 &baselines,
4664 &[],
4665 &[super::ResidentProjectExpressionDescriptor::column(0, 4)],
4666 &[],
4667 )
4668 .expect("recursive final delta copy may drive convergence");
4669 }
4670
4671 #[test]
4672 fn shared_validator_exempts_exact_pseudo_ops_from_slot_scope() {
4673 let status = super::ResidentOpDescriptor::test_status(super::ResidentTerminalStatus {
4674 code: 5,
4675 op_id: 81,
4676 resource_code: 7,
4677 iterations: 9,
4678 limit: 11,
4679 required: 13,
4680 capacity: 17,
4681 ..Default::default()
4682 })
4683 .unwrap();
4684 let trace = super::ResidentOpDescriptor::trace_delta(2, 3, None);
4685 let ops = [status, trace];
4686 let waves = [super::ResidentWaveDescriptor {
4687 first_op: 0,
4688 op_count: 2,
4689 ..Default::default()
4690 }];
4691 let regions = [super::ResidentRegionDescriptor {
4692 first_wave: 0,
4693 wave_count: 1,
4694 iteration_limit: 1,
4695 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4696 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4697 ..Default::default()
4698 }];
4699 super::validate_schedule_program(&[], &[], &ops, &waves, ®ions, &[], &[], &[], &[])
4700 .expect("exact pseudo ops do not reference relation slots");
4701
4702 let invalid_trace = [super::ResidentOpDescriptor {
4703 reserved: 1,
4704 ..trace
4705 }];
4706 let one_op_wave = [super::ResidentWaveDescriptor {
4707 first_op: 0,
4708 op_count: 1,
4709 ..Default::default()
4710 }];
4711 assert!(super::validate_schedule_program(
4712 &[],
4713 &[],
4714 &invalid_trace,
4715 &one_op_wave,
4716 ®ions,
4717 &[],
4718 &[],
4719 &[],
4720 &[],
4721 )
4722 .is_err());
4723 }
4724
4725 #[test]
4726 fn shared_validator_requires_zero_only_unit_and_scan_operands() {
4727 let slots = [super::ResidentRelationSlot {
4728 relation: super::ResidentRelationView {
4729 capacity: 1,
4730 ..Default::default()
4731 },
4732 generation: 3,
4733 flags: super::RESIDENT_SCHEDULE_SLOT_PERMANENT | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
4734 ..Default::default()
4735 }];
4736 let types = [Vec::new()];
4737 let waves = [super::ResidentWaveDescriptor {
4738 first_op: 0,
4739 op_count: 1,
4740 ..Default::default()
4741 }];
4742 let regions = [super::ResidentRegionDescriptor {
4743 first_wave: 0,
4744 wave_count: 1,
4745 iteration_limit: 1,
4746 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4747 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4748 first_slot: 0,
4749 slot_count: 1,
4750 generation_offset: 0,
4751 ..Default::default()
4752 }];
4753 let invalid_unit = [super::ResidentOpDescriptor {
4754 in0: 9,
4755 ..super::ResidentOpDescriptor::unit(91, 0, 3)
4756 }];
4757 assert!(super::validate_schedule_program(
4758 &slots,
4759 &types,
4760 &invalid_unit,
4761 &waves,
4762 ®ions,
4763 &[3],
4764 &[],
4765 &[],
4766 &[],
4767 )
4768 .is_err());
4769
4770 let invalid_scan = [super::ResidentOpDescriptor {
4771 in1: 9,
4772 ..super::ResidentOpDescriptor::scan(92, 0, 3)
4773 }];
4774 assert!(super::validate_schedule_program(
4775 &slots,
4776 &types,
4777 &invalid_scan,
4778 &waves,
4779 ®ions,
4780 &[3],
4781 &[],
4782 &[],
4783 &[],
4784 )
4785 .is_err());
4786 }
4787
4788 #[test]
4789 fn shared_validator_checks_set_and_join_layouts_and_workspace_envelopes() {
4790 let slot = |arity: u32, widths: &[u32], capacity: u32, generation: u32, permanent| {
4791 let mut relation = super::ResidentRelationView {
4792 arity,
4793 capacity,
4794 ..Default::default()
4795 };
4796 relation.widths[..widths.len()].copy_from_slice(widths);
4797 super::ResidentRelationSlot {
4798 relation,
4799 generation,
4800 flags: if permanent {
4801 super::RESIDENT_SCHEDULE_SLOT_PERMANENT | super::RESIDENT_SCHEDULE_SLOT_DEFINED
4802 } else {
4803 0
4804 },
4805 schema_tag: if widths == [4] { 1 } else { 2 },
4806 ..Default::default()
4807 }
4808 };
4809 let waves = [super::ResidentWaveDescriptor {
4810 first_op: 0,
4811 op_count: 1,
4812 ..Default::default()
4813 }];
4814 let region = super::ResidentRegionDescriptor {
4815 first_wave: 0,
4816 wave_count: 1,
4817 iteration_limit: 1,
4818 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
4819 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
4820 first_slot: 0,
4821 slot_count: 3,
4822 generation_offset: 0,
4823 ..Default::default()
4824 };
4825
4826 let set_slots = [
4827 slot(1, &[4], 3, 1, true),
4828 slot(1, &[4], 4, 2, true),
4829 slot(1, &[4], 7, 3, false),
4830 ];
4831 let set_types = [
4832 vec![ScalarType::U32],
4833 vec![ScalarType::U32],
4834 vec![ScalarType::U32],
4835 ];
4836 let union = [super::ResidentOpDescriptor {
4837 kind: super::ResidentScheduleOpKind::Union,
4838 out: 2,
4839 in0: 0,
4840 in1: 1,
4841 in0_generation: 1,
4842 in1_generation: 2,
4843 out_generation: 3,
4844 ..Default::default()
4845 }];
4846 let requirements = super::validate_schedule_program(
4847 &set_slots,
4848 &set_types,
4849 &union,
4850 &waves,
4851 &[region],
4852 &[1, 2, 3],
4853 &[],
4854 &[],
4855 &[],
4856 )
4857 .unwrap();
4858 assert_eq!(requirements.set_candidate_capacity, 7);
4859
4860 let join_slots = [
4861 slot(1, &[4], 5, 1, true),
4862 slot(1, &[8], 6, 2, true),
4863 slot(2, &[4, 8], 8, 3, false),
4864 ];
4865 let join_types = [
4866 vec![ScalarType::U32],
4867 vec![ScalarType::U64],
4868 vec![ScalarType::U32, ScalarType::U64],
4869 ];
4870 let invalid_join = [super::ResidentOpDescriptor {
4871 kind: super::ResidentScheduleOpKind::JoinInner,
4872 out: 2,
4873 in0: 0,
4874 in1: 1,
4875 in0_generation: 1,
4876 in1_generation: 2,
4877 out_generation: 3,
4878 left_key: 0,
4879 right_key: 0,
4880 ..Default::default()
4881 }];
4882 assert!(super::validate_schedule_program(
4883 &join_slots,
4884 &join_types,
4885 &invalid_join,
4886 &waves,
4887 &[region],
4888 &[1, 2, 3],
4889 &[],
4890 &[],
4891 &[],
4892 )
4893 .is_err());
4894 }
4895
4896 #[test]
4897 fn cuda_mirrors_flattened_descriptor_validation_with_subtract_bounds() {
4898 let cuda = include_str!("../../kernels/resident_schedule.cu");
4899 for required in [
4900 "op.aux_offset <= header->filter_comparison_count",
4901 "op.aux_count <= header->filter_comparison_count - op.aux_offset",
4902 "comparison.left_kind > 1",
4903 "comparison.right_kind > 1",
4904 "comparison.op > 5",
4905 "comparison.reserved_zero != 0",
4906 "comparison.reserved_one != 0",
4907 "comparison.left_column >= in0.arity",
4908 "comparison.width != in0.widths[comparison.left_column]",
4909 "op.aux_offset <= header->project_expression_count",
4910 "op.aux_count <= header->project_expression_count - op.aux_offset",
4911 "expression.kind > 1",
4912 "expression.reserved != 0",
4913 "expression.column >= in0.arity",
4914 "expression.width != out.widths[column]",
4915 ] {
4916 assert!(
4917 cuda.contains(required),
4918 "missing device validation: {required}"
4919 );
4920 }
4921 }
4922
4923 #[test]
4924 fn generation_baseline_ranges_reject_overflow_and_out_of_bounds() {
4925 let valid = [
4926 super::ResidentRegionDescriptor {
4927 slot_count: 2,
4928 generation_offset: 0,
4929 ..Default::default()
4930 },
4931 super::ResidentRegionDescriptor {
4932 slot_count: 3,
4933 generation_offset: 2,
4934 ..Default::default()
4935 },
4936 ];
4937 super::validate_generation_baseline_ranges(&valid, 5)
4938 .expect("concatenated generation baselines");
4939
4940 let out_of_bounds = [super::ResidentRegionDescriptor {
4941 slot_count: 6,
4942 generation_offset: 0,
4943 ..Default::default()
4944 }];
4945 assert_eq!(
4946 super::validate_generation_baseline_ranges(&out_of_bounds, 5)
4947 .expect_err("range must exceed generation table")
4948 .to_string(),
4949 "Kernel error: resident schedule generation baseline range is invalid"
4950 );
4951
4952 let overflow = [
4953 super::ResidentRegionDescriptor {
4954 slot_count: u32::MAX,
4955 generation_offset: 0,
4956 ..Default::default()
4957 },
4958 super::ResidentRegionDescriptor {
4959 slot_count: 1,
4960 generation_offset: u32::MAX,
4961 ..Default::default()
4962 },
4963 ];
4964 assert_eq!(
4965 super::validate_generation_baseline_ranges(&overflow, u32::MAX)
4966 .expect_err("range arithmetic must be checked")
4967 .to_string(),
4968 "Kernel error: resident schedule generation baseline range overflow"
4969 );
4970 }
4971
4972 #[test]
4973 fn generation_baselines_exactly_concatenate_region_scopes() {
4974 let first = super::ResidentRegionDescriptor {
4975 slot_count: 1,
4976 generation_offset: 0,
4977 ..Default::default()
4978 };
4979 let second = super::ResidentRegionDescriptor {
4980 slot_count: 1,
4981 generation_offset: 1,
4982 ..Default::default()
4983 };
4984 super::validate_generation_baseline_ranges(&[first, second], 2)
4985 .expect("exact concatenation");
4986
4987 assert!(super::validate_generation_baseline_ranges(
4988 &[
4989 first,
4990 super::ResidentRegionDescriptor {
4991 generation_offset: 2,
4992 ..second
4993 },
4994 ],
4995 3,
4996 )
4997 .is_err());
4998 assert!(super::validate_generation_baseline_ranges(
4999 &[
5000 first,
5001 super::ResidentRegionDescriptor {
5002 generation_offset: 0,
5003 ..second
5004 },
5005 ],
5006 2,
5007 )
5008 .is_err());
5009 assert!(super::validate_generation_baseline_ranges(&[first, second], 3).is_err());
5010 }
5011
5012 #[test]
5013 fn generation_metadata_appends_schema_defaults_and_derives_baseline_count() {
5014 let metadata =
5015 super::build_generation_metadata(&[4, 7, 9], &[101, 202]).expect("generation metadata");
5016 assert_eq!(metadata, vec![4, 7, 9, 101, 202]);
5017 assert_eq!(
5018 super::generation_baseline_count_from_metadata(5, 2)
5019 .expect("three generation baselines"),
5020 3
5021 );
5022 assert_eq!(
5023 super::generation_baseline_count_from_metadata(3, 0).expect("no schema-default tail"),
5024 3
5025 );
5026 assert!(super::generation_baseline_count_from_metadata(1, 2).is_err());
5027 }
5028
5029 #[test]
5030 fn schema_winner_replay_reset_restores_defaults_before_ordered_marks() {
5031 let defaults = [10, 20];
5032 let mut seen = [1, 1];
5033 let mut winners = [99, 98];
5034 super::reset_schema_winner_state(&defaults, &mut seen, &mut winners)
5035 .expect("reset replay state");
5036 assert_eq!(seen, [0, 0]);
5037 assert_eq!(winners, defaults);
5038
5039 super::mark_schema_winner_model(3, 10, &mut seen[0], &mut winners[0]);
5040 super::mark_schema_winner_model(2, 30, &mut seen[0], &mut winners[0]);
5041 assert_eq!(winners[0], 10, "existing nonempty head retains its default");
5042
5043 super::reset_schema_winner_state(&defaults, &mut seen, &mut winners)
5044 .expect("reset second replay");
5045 super::mark_schema_winner_model(0, 20, &mut seen[1], &mut winners[1]);
5046 super::mark_schema_winner_model(2, 40, &mut seen[1], &mut winners[1]);
5047 assert_eq!(
5048 winners[1], 40,
5049 "empty head accepts its first later contribution"
5050 );
5051 }
5052
5053 #[test]
5054 fn generation_baselines_are_concatenated_in_region_scope_order() {
5055 let mut regions = [
5056 super::ResidentRegionDescriptor {
5057 first_slot: 0,
5058 slot_count: 2,
5059 ..Default::default()
5060 },
5061 super::ResidentRegionDescriptor {
5062 first_slot: 1,
5063 slot_count: 2,
5064 ..Default::default()
5065 },
5066 ];
5067 let baselines = super::build_generation_baselines(&mut regions, &[11, 22, 33])
5068 .expect("generation baseline table");
5069 assert_eq!(baselines, vec![11, 22, 22, 33]);
5070 assert_eq!(regions[0].generation_offset, 0);
5071 assert_eq!(regions[1].generation_offset, 2);
5072
5073 let mut invalid = [super::ResidentRegionDescriptor {
5074 first_slot: 2,
5075 slot_count: 2,
5076 ..Default::default()
5077 }];
5078 assert_eq!(
5079 super::build_generation_baselines(&mut invalid, &[11, 22, 33])
5080 .expect_err("slot scope must be bounded")
5081 .to_string(),
5082 "Kernel error: resident schedule generation baseline slot scope is invalid"
5083 );
5084 }
5085
5086 #[test]
5087 fn cuda_checks_generation_baseline_range_before_resetting_slots() {
5088 let source = include_str!("../../kernels/resident_schedule.cu");
5089 let guard = source
5090 .find("const bool generation_range_valid")
5091 .expect("device generation range guard");
5092 let read = source
5093 .find("generation_metadata[region.generation_offset + index]")
5094 .expect("device generation baseline read");
5095 assert!(
5096 guard < read,
5097 "generation baseline read must follow its bounds guard"
5098 );
5099 assert!(source
5100 .contains("region.slot_count <= generation_base_count - region.generation_offset"));
5101 }
5102
5103 #[test]
5104 fn schema_winner_encoding_uses_exact_receipt_shape_and_head_bounds() {
5105 assert_eq!(
5106 super::checked_schedule_head_count(8, 76).expect("two heads"),
5107 2
5108 );
5109 assert_eq!(
5110 super::checked_schedule_winner_count(8, 76, 2).expect("matching winner state"),
5111 2
5112 );
5113 assert!(super::checked_schedule_winner_count(8, 76, 1).is_err());
5114 for (count, bytes) in [(1, 48), (5, 64), (6, 64)] {
5115 assert!(super::checked_schedule_head_count(count, bytes).is_err());
5116 }
5117
5118 let marked = super::ResidentOpDescriptor::default().with_schema_winner(1, 77);
5119 assert_eq!(marked.flags, super::RESIDENT_SCHEDULE_OP_MARK_SCHEMA_WINNER);
5120 super::validate_schema_winner_encoding(&marked, 2).expect("bounded winner mark");
5121
5122 let out_of_bounds = marked.with_schema_winner(2, 88);
5123 assert!(super::validate_schema_winner_encoding(&out_of_bounds, 2).is_err());
5124
5125 let unmarked_payload = super::ResidentOpDescriptor {
5126 schema_winner_id: 77,
5127 ..Default::default()
5128 };
5129 assert!(super::validate_schema_winner_encoding(&unmarked_payload, 2).is_err());
5130 }
5131
5132 #[test]
5133 fn cuda_schema_winner_mark_is_count_gated_and_sticky() {
5134 let source = include_str!("../../kernels/resident_schedule.cu");
5135 for required in [
5136 "const bool receipt_shape_valid",
5137 "*head_count = (header->receipt_count - 4) / 2",
5138 "header->schema_winner_count != *head_count",
5139 "op.schema_winner_head >= head_count",
5140 "*device_ptr<const uint32_t>(output.num_rows) != 0",
5141 "atomicCAS(&schema_seen_nonempty[op.schema_winner_head], 0U, 1U)",
5142 "schema_winner_ids[op.schema_winner_head] = op.schema_winner_id",
5143 ] {
5144 assert!(
5145 source.contains(required),
5146 "missing CUDA winner fragment: {required}"
5147 );
5148 }
5149 let count_gate = source
5150 .find("*device_ptr<const uint32_t>(output.num_rows) != 0")
5151 .expect("winner count gate");
5152 let compare_exchange = source
5153 .find("atomicCAS(&schema_seen_nonempty[op.schema_winner_head], 0U, 1U)")
5154 .expect("sticky winner compare-exchange");
5155 assert!(count_gate < compare_exchange);
5156 let receipt_guard = source
5157 .find("if (!receipt_shape_valid) return false;")
5158 .unwrap();
5159 let head_derivation = source
5160 .find("*head_count = (header->receipt_count - 4) / 2")
5161 .unwrap();
5162 assert!(receipt_guard < head_derivation);
5163 }
5164
5165 #[test]
5166 fn cuda_initialize_resets_schema_winners_from_metadata_tail_before_waves() {
5167 let source = include_str!("../../kernels/resident_schedule.cu");
5168 for required in [
5169 "header->generation_metadata_count < *head_count",
5170 "const uint32_t generation_base_count = generation_metadata_shape_valid",
5171 "? header->generation_metadata_count - head_count : 0;",
5172 "schema_seen_nonempty[head] = 0U;",
5173 "schema_winner_ids[head] =",
5174 "generation_metadata[generation_base_count + head];",
5175 ] {
5176 assert!(
5177 source.contains(required),
5178 "missing CUDA replay-reset fragment: {required}"
5179 );
5180 }
5181 let reset = source
5182 .find("schema_seen_nonempty[head] = 0U;")
5183 .expect("schema seen reset");
5184 let waves = source
5185 .find("for (uint32_t wave_offset = 0; wave_offset < safe_wave_count; ++wave_offset)")
5186 .expect("wave loop");
5187 assert!(
5188 reset < waves,
5189 "schema winners reset before the first operation wave"
5190 );
5191 }
5192
5193 #[test]
5194 fn cuda_validator_enforces_region_scope_and_scan_only_same_slot_aliasing() {
5195 let source = include_str!("../../kernels/resident_schedule.cu");
5196 for required in [
5197 "const ResidentRegionDescriptor ®ion,",
5198 "const uint32_t region_slot_end = region.first_slot + region.slot_count;",
5199 "op.kind != kOpScan && (op.out == op.in0 ||",
5200 "op.out < region.first_slot || op.out >= region_slot_end",
5201 "op.in0 != 0 || op.in1 != 0",
5202 ] {
5203 assert!(
5204 source.contains(required),
5205 "missing CUDA scope or alias fragment: {required}"
5206 );
5207 }
5208 }
5209
5210 #[test]
5211 fn cuda_validator_mirrors_physical_payload_and_workspace_checks() {
5212 let source = include_str!("../../kernels/resident_schedule.cu");
5213 for required in [
5214 "op.reserved != 0 ||",
5215 "output.relation.capacity > 65536 ||",
5216 "op.in1 != 0 || op.in1_generation != 0 ||",
5217 "op.left_key != 0 || op.right_key != 0",
5218 "static_cast<uint64_t>(in0.capacity) + in1.capacity >",
5219 "header->set_candidate_capacity",
5220 "expected_arity > kMaxArity",
5221 ] {
5222 assert!(
5223 source.contains(required),
5224 "missing CUDA physical-envelope fragment: {required}"
5225 );
5226 }
5227 }
5228
5229 #[test]
5230 fn cuda_region_reset_rejects_unknown_or_conflicting_slot_flags() {
5231 let source = include_str!("../../kernels/resident_schedule.cu");
5232 for required in [
5233 "slot.flags & ~(kSourceSlot | kPermanentSlot | kDefinedSlot)",
5234 "(slot.flags & kSourceSlot) != 0 &&",
5235 "(slot.flags & kPermanentSlot) != 0",
5236 ] {
5237 assert!(
5238 source.contains(required),
5239 "missing CUDA slot-flag validation: {required}"
5240 );
5241 }
5242 }
5243
5244 #[test]
5245 fn test_status_descriptor_round_trips_the_full_terminal_payload() {
5246 let status = super::ResidentTerminalStatus {
5247 code: 3,
5248 op_id: 0x1020_3040,
5249 resource_code: 0x5060_7080,
5250 iterations: 0x90a0_b0c0,
5251 limit: 0xd0e0_f001,
5252 reserved: 0,
5253 required: 0x1122_3344_5566_7788,
5254 capacity: 0x99aa_bbcc_ddee_ff00,
5255 };
5256 let descriptor =
5257 super::ResidentOpDescriptor::test_status(status).expect("test status descriptor");
5258 assert_eq!(descriptor.kind, super::ResidentScheduleOpKind::TestStatus);
5259 assert_eq!(super::decode_test_status(&descriptor).unwrap(), status);
5260
5261 let invalid_unused = super::ResidentOpDescriptor {
5262 right_key: 1,
5263 ..descriptor
5264 };
5265 assert!(super::decode_test_status(&invalid_unused).is_err());
5266
5267 let invalid_reserved = super::ResidentTerminalStatus {
5268 reserved: 1,
5269 ..status
5270 };
5271 assert!(super::ResidentOpDescriptor::test_status(invalid_reserved).is_err());
5272 }
5273
5274 #[test]
5275 fn cuda_test_status_descriptor_publishes_both_u64_fields_without_auxiliary_storage() {
5276 let source = include_str!("../../kernels/resident_schedule.cu");
5277 for required in [
5278 "constexpr uint32_t kOpTestStatus = 8",
5279 "op.kind == kOpTestStatus",
5280 "static_cast<uint64_t>(op.out_generation) |",
5281 "(static_cast<uint64_t>(op.aux_offset) << 32)",
5282 "static_cast<uint64_t>(op.aux_count) |",
5283 "(static_cast<uint64_t>(op.left_key) << 32)",
5284 "status->iterations = op.in1",
5285 "status->limit = op.in0_generation",
5286 ] {
5287 assert!(
5288 source.contains(required),
5289 "missing CUDA TestStatus fragment: {required}"
5290 );
5291 }
5292 }
5293
5294 #[test]
5295 fn trace_delta_descriptor_carries_an_optional_semantic_guard() {
5296 let descriptor = super::ResidentOpDescriptor::trace_delta(2, 3, None);
5297 assert_eq!(descriptor.kind, super::ResidentScheduleOpKind::TraceDelta);
5298 assert_eq!(
5299 super::decode_trace_delta(&descriptor).unwrap(),
5300 (2, 3, None)
5301 );
5302
5303 let guarded = super::ResidentOpDescriptor::trace_delta(5, 7, Some((11, 13)));
5304 assert_eq!(guarded.flags, super::RESIDENT_SCHEDULE_TRACE_SEMANTIC_GUARD);
5305 assert_eq!(guarded.in0, 11);
5306 assert_eq!(guarded.in0_generation, 13);
5307 assert_eq!(
5308 super::decode_trace_delta(&guarded).unwrap(),
5309 (5, 7, Some((11, 13)))
5310 );
5311
5312 let invalid = super::ResidentOpDescriptor {
5313 op_id: 1,
5314 ..guarded
5315 };
5316 assert!(super::decode_trace_delta(&invalid).is_err());
5317 }
5318
5319 #[test]
5320 fn cuda_trace_delta_executes_after_an_earlier_terminal_status() {
5321 let source = include_str!("../../kernels/resident_schedule.cu");
5322 for required in [
5323 "constexpr uint32_t kOpTraceDelta = 9",
5324 "constexpr uint32_t kOpTraceSemanticGuard = 1",
5325 "if (op.kind == kOpTraceDelta) {",
5326 "atomicAdd(device_ptr<uint32_t>(header->scan_trace), op.scan_delta)",
5327 "atomicAdd(device_ptr<uint32_t>(header->filter_trace), op.filter_delta)",
5328 "const bool semantic_active =",
5329 "*device_ptr<const uint32_t>(input_zero.num_rows) != 0",
5330 "header->semantic_scan_trace",
5331 "header->semantic_filter_trace",
5332 ] {
5333 assert!(
5334 source.contains(required),
5335 "missing CUDA TraceDelta fragment: {required}"
5336 );
5337 }
5338 assert!(!source.contains("status->code == kRunning && op.scan_delta"));
5339 assert!(!source.contains("status->code == kRunning && op.filter_delta"));
5340 }
5341
5342 #[test]
5343 fn cuda_set_ordering_compacts_in_parallel_and_uses_bounded_merge_passes() {
5344 let source = include_str!("../../kernels/resident_schedule.cu");
5345 for required in [
5346 "compact_set_winners_by_tile",
5347 "sort_set_reference_tiles",
5348 "merge_set_reference_runs",
5349 "kSetReferenceTileSize = 1024",
5350 "set_workspace_shape_valid",
5351 "set_slot_count >= 2ULL * header->set_candidate_capacity",
5352 ] {
5353 assert!(
5354 source.contains(required),
5355 "missing bounded set-ordering fragment: {required}"
5356 );
5357 }
5358 assert!(
5359 !source.contains("for (uint32_t slot = 0; slot <= header->set_slot_mask; ++slot)"),
5360 "set winners must not be compacted by one thread"
5361 );
5362 assert!(
5363 !source.contains("for (uint32_t width = 2; width <= sort_size; width <<= 1)"),
5364 "set ordering must not use a grid-wide bitonic network"
5365 );
5366 }
5367
5368 #[test]
5369 fn cuda_packs_the_single_receipt_only_in_the_final_region() {
5370 let source = include_str!("../../kernels/resident_schedule.cu");
5371 let guard = source
5372 .find("if (finalizes && global_rank() == 0)")
5373 .expect("final-region receipt guard");
5374 let pack = source
5375 .find("*reinterpret_cast<ResidentTerminalStatus *>(receipt) = *status")
5376 .expect("terminal receipt pack");
5377 assert!(guard < pack);
5378 assert_eq!(
5379 source
5380 .matches("*reinterpret_cast<ResidentTerminalStatus *>(receipt) = *status")
5381 .count(),
5382 1,
5383 "there must be one receipt pack path"
5384 );
5385 }
5386
5387 #[test]
5388 fn slot_definedness_distinguishes_scratch_permanent_and_failed_writes() {
5389 let scratch = super::reset_slot_flags(super::RESIDENT_SCHEDULE_SLOT_DEFINED);
5390 assert_eq!(scratch, 0);
5391 assert!(!super::slot_input_is_ready(scratch, 7, 7));
5392
5393 let permanent = super::reset_slot_flags(super::RESIDENT_SCHEDULE_SLOT_PERMANENT);
5394 assert_eq!(
5395 permanent,
5396 super::RESIDENT_SCHEDULE_SLOT_PERMANENT | super::RESIDENT_SCHEDULE_SLOT_DEFINED
5397 );
5398 assert!(super::slot_input_is_ready(permanent, 7, 7));
5399
5400 let source = super::reset_slot_flags(super::RESIDENT_SCHEDULE_SLOT_SOURCE);
5401 assert!(super::slot_input_is_ready(source, 7, 7));
5402 assert!(!super::slot_output_generation_is_valid(source, 7, 7));
5403
5404 assert!(super::slot_output_generation_is_valid(scratch, 7, 7));
5405 assert!(super::slot_output_generation_is_valid(scratch, 7, 8));
5406 assert!(!super::slot_output_generation_is_valid(scratch, 7, 9));
5407 assert!(!super::slot_output_generation_is_valid(
5408 scratch,
5409 u32::MAX,
5410 0
5411 ));
5412
5413 assert_eq!(super::finish_slot_write(scratch, false), scratch);
5414 assert_eq!(
5415 super::finish_slot_write(scratch, true),
5416 super::RESIDENT_SCHEDULE_SLOT_DEFINED
5417 );
5418 }
5419
5420 #[test]
5421 fn cuda_slot_definedness_is_reset_checked_and_set_after_success() {
5422 let source = include_str!("../../kernels/resident_schedule.cu");
5423 for required in [
5424 "constexpr uint32_t kPermanentSlot = 2",
5425 "constexpr uint32_t kDefinedSlot = 4",
5426 "slot.flags |= kDefinedSlot",
5427 "slot.flags &= ~kDefinedSlot",
5428 "(input.flags & kDefinedSlot) != 0",
5429 "slots[op.out].flags |= kDefinedSlot",
5430 "slots[op.out].generation = op.out_generation",
5431 ] {
5432 assert!(
5433 source.contains(required),
5434 "missing CUDA definedness fragment: {required}"
5435 );
5436 }
5437 let execution = source.find("execute_filter(grid").unwrap();
5438 let define = source.find("slots[op.out].flags |= kDefinedSlot").unwrap();
5439 assert!(
5440 execution < define,
5441 "output must become defined only after execution"
5442 );
5443 }
5444
5445 #[test]
5446 fn schedule_metadata_manifest_uses_exact_flattened_table_bytes() {
5447 assert_eq!(
5448 super::resident_schedule_metadata_device_bytes(0, 0, 0, 0, 0, 0, 0).unwrap(),
5449 724
5450 );
5451 assert_eq!(
5452 super::resident_schedule_metadata_device_bytes(2, 3, 4, 5, 6, 0, 2).unwrap(),
5453 1_328
5454 );
5455 let generation_count = 6;
5456 let schema_default_count = 2;
5457 assert_eq!(
5458 super::resident_schedule_metadata_device_bytes(
5459 2,
5460 3,
5461 4,
5462 5,
5463 generation_count + schema_default_count,
5464 0,
5465 2,
5466 )
5467 .unwrap(),
5468 1_336
5469 );
5470 assert!(
5471 super::resident_schedule_metadata_device_bytes(usize::MAX, 1, 1, 1, 1, 1, 1).is_err()
5472 );
5473 }
5474
5475 #[test]
5476 fn additive_schedule_api_records_one_region_into_the_existing_stream() {
5477 let _record: unsafe fn(
5478 &super::CudaKernelProvider,
5479 &super::ResidentScheduleDeviceProgram,
5480 u32,
5481 Option<&crate::cuda_graph::ConditionalCudaGraphBody>,
5482 &CudaStream,
5483 ) -> xlog_core::Result<()> =
5484 super::CudaKernelProvider::record_resident_schedule_region_on_stream;
5485 }
5486
5487 #[test]
5488 fn additive_schedule_unsafe_contract_covers_owners_recorder_and_graph_identity() {
5489 let source = include_str!("resident_schedule.rs");
5490 let start = source
5491 .find("/// Record one compact scheduler region into a graph owned by the caller.")
5492 .expect("additive record docs");
5493 let end = source[start..]
5494 .find("pub unsafe fn record_resident_schedule_region_on_stream")
5495 .map(|offset| start + offset)
5496 .expect("additive record signature");
5497 let safety = source[start..end]
5498 .lines()
5499 .map(|line| line.trim_start().trim_start_matches("///").trim())
5500 .collect::<Vec<_>>()
5501 .join(" ");
5502 for required in [
5503 "register the program, every slot and external owner, and every indirect receipt pointee",
5504 "before domain-bound preflight",
5505 "through graph destruction and completion of all in-flight work",
5506 "domain-bound preflight and domain-bound commit",
5507 "conditional body passed here must be the one minted for the enclosing graph",
5508 ] {
5509 assert!(
5510 safety.contains(required),
5511 "missing additive safety obligation: {required}"
5512 );
5513 }
5514 }
5515
5516 #[test]
5517 fn additive_schedule_uses_one_sealed_execution_domain_and_bound_recorder() {
5518 let _bind: fn(
5519 &super::CudaKernelProvider,
5520 Arc<XlogDeviceRuntime>,
5521 crate::device_runtime::StreamId,
5522 Arc<CudaStream>,
5523 ) -> xlog_core::Result<super::ResidentExecutionDomain> =
5524 super::CudaKernelProvider::bind_resident_execution_domain;
5525 let _recorder: fn(&super::ResidentExecutionDomain) -> crate::launch::LaunchRecorder =
5526 super::ResidentExecutionDomain::new_strict_recorder;
5527 let _preflight: fn(
5528 &super::ResidentExecutionDomain,
5529 &mut crate::launch::LaunchRecorder,
5530 ) -> xlog_core::Result<()> = super::ResidentExecutionDomain::preflight;
5531 let _commit: fn(
5532 &super::ResidentExecutionDomain,
5533 crate::launch::LaunchRecorder,
5534 ) -> xlog_core::Result<()> = super::ResidentExecutionDomain::commit;
5535 }
5536
5537 #[test]
5538 fn allocation_owner_validation_uses_checked_live_block_ranges() {
5539 let block = crate::device_runtime::BlockId {
5540 ptr: 0x1000,
5541 generation: crate::device_runtime::Generation(1),
5542 alloc_stream: crate::device_runtime::StreamId(2),
5543 device_ordinal: 3,
5544 };
5545 assert_eq!(
5546 super::validate_runtime_allocation_fields(
5547 7,
5548 0x1004,
5549 8,
5550 block,
5551 32,
5552 crate::device_runtime::BlockState::Live,
5553 7,
5554 3,
5555 )
5556 .unwrap(),
5557 (0x1004, 0x100c)
5558 );
5559 assert!(super::validate_runtime_allocation_fields(
5560 8,
5561 0x1004,
5562 8,
5563 block,
5564 32,
5565 crate::device_runtime::BlockState::Live,
5566 7,
5567 3,
5568 )
5569 .is_err());
5570 assert!(super::validate_runtime_allocation_fields(
5571 7,
5572 u64::MAX - 1,
5573 4,
5574 block,
5575 32,
5576 crate::device_runtime::BlockState::Live,
5577 7,
5578 3,
5579 )
5580 .is_err());
5581 assert!(super::validate_runtime_allocation_fields(
5582 7,
5583 0x1004,
5584 8,
5585 block,
5586 32,
5587 crate::device_runtime::BlockState::Retired,
5588 7,
5589 3,
5590 )
5591 .is_err());
5592 }
5593
5594 #[test]
5595 fn allocation_inventory_rejects_partial_aliases_but_allows_adjacency() {
5596 let mut ranges = Vec::new();
5597 super::insert_nonoverlapping_allocation_range(&mut ranges, (0x1000, 0x1010))
5598 .expect("first allocation");
5599 super::insert_nonoverlapping_allocation_range(&mut ranges, (0x1010, 0x1020))
5600 .expect("adjacent allocation");
5601 assert!(
5602 super::insert_nonoverlapping_allocation_range(&mut ranges, (0x1008, 0x1018)).is_err()
5603 );
5604 }
5605
5606 #[test]
5607 fn receipt_slot_mapping_requires_exact_unique_permanent_targets() {
5608 let permanent =
5609 super::RESIDENT_SCHEDULE_SLOT_PERMANENT | super::RESIDENT_SCHEDULE_SLOT_DEFINED;
5610 let source = super::RESIDENT_SCHEDULE_SLOT_SOURCE | super::RESIDENT_SCHEDULE_SLOT_DEFINED;
5611 let flags = [source, permanent, permanent, 0];
5612
5613 assert_eq!(
5614 super::validate_receipt_slot_mapping(&[], &flags, 0).unwrap(),
5615 Vec::<usize>::new()
5616 );
5617 assert_eq!(
5618 super::validate_receipt_slot_mapping(&[2], &flags, 1).unwrap(),
5619 vec![2]
5620 );
5621 assert_eq!(
5622 super::validate_receipt_slot_mapping(&[2, 1], &flags, 2).unwrap(),
5623 vec![2, 1]
5624 );
5625 assert!(super::validate_receipt_slot_mapping(&[1], &flags, 2).is_err());
5626 assert!(super::validate_receipt_slot_mapping(&[1, 1], &flags, 2).is_err());
5627 assert!(super::validate_receipt_slot_mapping(&[0], &flags, 1).is_err());
5628 assert!(super::validate_receipt_slot_mapping(&[3], &flags, 1).is_err());
5629 assert!(super::validate_receipt_slot_mapping(&[4], &flags, 1).is_err());
5630 }
5631
5632 #[test]
5633 fn device_program_construction_consumes_the_runtime_reservation_and_external_bindings() {
5634 let _prepare: for<'a> fn(
5635 &super::CudaKernelProvider,
5636 &super::ResidentExecutionDomain,
5637 &[super::ResidentScheduleSlotBinding<'a>],
5638 &[super::ResidentOpDescriptor],
5639 &[super::ResidentWaveDescriptor],
5640 &[super::ResidentRegionDescriptor],
5641 &[u32],
5642 &[super::ResidentFilterComparisonDescriptor],
5643 &[super::ResidentProjectExpressionDescriptor],
5644 &[u32],
5645 super::ResidentScheduleExternalBindings<'a>,
5646 &mut crate::memory::GpuMemoryReservation,
5647 )
5648 -> xlog_core::Result<super::ResidentScheduleDeviceProgram> =
5649 super::CudaKernelProvider::prepare_resident_schedule_program_in_reservation;
5650 }
5651
5652 #[test]
5653 fn additive_schedule_metadata_and_external_owners_have_strict_recorder_apis() {
5654 fn record_slot(
5655 slot: &super::ResidentScheduleSlotBinding<'_>,
5656 recorder: &mut crate::launch::LaunchRecorder,
5657 ) {
5658 slot.record_uses(recorder)
5659 }
5660 fn record_external(
5661 external: &super::ResidentScheduleExternalBindings<'_>,
5662 recorder: &mut crate::launch::LaunchRecorder,
5663 ) {
5664 external.record_uses(recorder)
5665 }
5666 let _program: fn(
5667 &super::ResidentScheduleDeviceProgram,
5668 &mut crate::launch::LaunchRecorder,
5669 ) = super::ResidentScheduleDeviceProgram::record_uses;
5670 let _slot = record_slot;
5671 let _external = record_external;
5672 }
5673
5674 #[test]
5675 fn slot_reset_preserves_source_and_permanent_counts_and_clears_scratch() {
5676 let source = super::reset_slot_state_for_region(super::RESIDENT_SCHEDULE_SLOT_SOURCE, 9, 4);
5677 assert_eq!(
5678 source,
5679 (
5680 super::RESIDENT_SCHEDULE_SLOT_SOURCE | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
5681 9,
5682 4,
5683 )
5684 );
5685 let permanent =
5686 super::reset_slot_state_for_region(super::RESIDENT_SCHEDULE_SLOT_PERMANENT, 7, 3);
5687 assert_eq!(
5688 permanent,
5689 (
5690 super::RESIDENT_SCHEDULE_SLOT_PERMANENT | super::RESIDENT_SCHEDULE_SLOT_DEFINED,
5691 7,
5692 3,
5693 )
5694 );
5695 assert_eq!(super::reset_slot_state_for_region(0, 11, 8), (0, 11, 0));
5696 }
5697
5698 #[test]
5699 fn cuda_reset_and_recorder_keep_source_count_read_only() {
5700 let cuda = include_str!("../../kernels/resident_schedule.cu");
5701 let reset_start = cuda
5702 .find("for (uint32_t index = 0; index < region.slot_count; ++index)")
5703 .expect("slot reset loop");
5704 let reset_end = cuda[reset_start..]
5705 .find("if (status->code == kRunning && recursive)")
5706 .map(|offset| reset_start + offset)
5707 .expect("slot reset end");
5708 let reset = &cuda[reset_start..reset_end];
5709 assert!(!reset.contains("slot.initial_count"));
5710 assert_eq!(
5711 reset
5712 .matches("*device_ptr<uint32_t>(slot.relation.num_rows) = 0")
5713 .count(),
5714 1,
5715 "only the scratch branch may reset a count word"
5716 );
5717
5718 let rust = include_str!("resident_schedule.rs");
5719 let source_arm = rust
5720 .find("Self::Source { buffer, .. } => {")
5721 .expect("source recorder arm");
5722 let resident_arm = rust[source_arm..]
5723 .find("Self::Resident { buffer, .. } => {")
5724 .map(|offset| source_arm + offset)
5725 .expect("resident recorder arm");
5726 let source_recorder = &rust[source_arm..resident_arm];
5727 assert!(source_recorder.contains("recorder.read(buffer.num_rows_device())"));
5728 assert!(!source_recorder.contains("read_write(buffer.num_rows_device())"));
5729 }
5730
5731 #[test]
5732 fn operation_kind_api_expresses_typed_unit_and_scan_leaves() {
5733 let unit = super::ResidentOpDescriptor::unit(101, 3, 7);
5734 assert_eq!(unit.kind, super::ResidentScheduleOpKind::Unit);
5735 assert_eq!(unit.op_id, 101);
5736 assert_eq!(unit.out, 3);
5737 assert_eq!(unit.out_generation, 7);
5738
5739 let scan = super::ResidentOpDescriptor::scan(102, 4, 9);
5740 assert_eq!(scan.kind, super::ResidentScheduleOpKind::Scan);
5741 assert_eq!(scan.op_id, 102);
5742 assert_eq!(scan.in0, 4);
5743 assert_eq!(scan.in0_generation, 9);
5744 assert_eq!(scan.out, 4);
5745 assert_eq!(scan.out_generation, 9);
5746 }
5747
5748 #[test]
5749 fn initialization_scope_must_cover_every_relation_slot() {
5750 let full = super::ResidentRegionDescriptor {
5751 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE,
5752 first_slot: 0,
5753 slot_count: 3,
5754 ..Default::default()
5755 };
5756 super::validate_initialization_scope(&full, 3).expect("full initialization scope");
5757
5758 let partial = super::ResidentRegionDescriptor {
5759 slot_count: 2,
5760 ..full
5761 };
5762 assert_eq!(
5763 super::validate_initialization_scope(&partial, 3)
5764 .expect_err("partial initialization scope")
5765 .to_string(),
5766 "Kernel error: resident schedule initialization must cover every relation slot"
5767 );
5768 }
5769
5770 #[test]
5771 fn region_control_placement_is_exact_before_materialization() {
5772 let region = |flags, first_wave, iteration_limit| super::ResidentRegionDescriptor {
5773 first_wave,
5774 wave_count: 1,
5775 iteration_limit,
5776 op_id: first_wave,
5777 flags,
5778 first_slot: 0,
5779 slot_count: 2,
5780 generation_offset: first_wave * 2,
5781 };
5782
5783 assert!(super::validate_region_control_and_ranges(
5784 &[region(
5785 super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5786 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
5787 0,
5788 1,
5789 )],
5790 1,
5791 2,
5792 )
5793 .is_ok());
5794
5795 let mut valid = [
5796 region(
5797 super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5798 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
5799 0,
5800 5,
5801 ),
5802 region(super::RESIDENT_SCHEDULE_REGION_RECURSIVE, 1, 5),
5803 region(super::RESIDENT_SCHEDULE_REGION_FINALIZE, 2, 1),
5804 ];
5805 valid[1].op_id = valid[0].op_id;
5806 assert!(super::validate_region_control_and_ranges(&valid, 3, 2).is_ok());
5807
5808 for invalid in [
5809 vec![region(super::RESIDENT_SCHEDULE_REGION_FINALIZE, 0, 1)],
5810 vec![region(super::RESIDENT_SCHEDULE_REGION_INITIALIZE, 0, 1)],
5811 vec![
5812 region(super::RESIDENT_SCHEDULE_REGION_INITIALIZE, 0, 1),
5813 region(
5814 super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5815 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
5816 1,
5817 1,
5818 ),
5819 ],
5820 vec![region(
5821 super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5822 | super::RESIDENT_SCHEDULE_REGION_RECURSIVE
5823 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
5824 0,
5825 1,
5826 )],
5827 vec![
5828 region(
5829 super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5830 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
5831 0,
5832 5,
5833 ),
5834 region(
5835 super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN
5836 | super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
5837 1,
5838 5,
5839 ),
5840 region(super::RESIDENT_SCHEDULE_REGION_FINALIZE, 2, 1),
5841 ],
5842 vec![region(
5843 super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5844 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
5845 0,
5846 2,
5847 )],
5848 ] {
5849 assert!(
5850 super::validate_region_control_and_ranges(&invalid, invalid.len() as u32, 2)
5851 .is_err()
5852 );
5853 }
5854 }
5855
5856 #[test]
5857 fn waves_exactly_partition_operations_before_materialization() {
5858 let wave = |first_op, op_count| super::ResidentWaveDescriptor {
5859 first_op,
5860 op_count,
5861 flags: 0,
5862 reserved: 0,
5863 };
5864 assert!(super::validate_wave_partition(&[], 0).is_ok());
5865 assert!(super::validate_wave_partition(&[wave(0, 2), wave(2, 1)], 3).is_ok());
5866 for invalid in [
5867 vec![wave(1, 2)],
5868 vec![wave(0, 1), wave(2, 1)],
5869 vec![wave(0, 2), wave(1, 1)],
5870 vec![wave(0, 1)],
5871 ] {
5872 assert!(super::validate_wave_partition(&invalid, 3).is_err());
5873 }
5874 assert!(super::validate_wave_partition(
5875 &[super::ResidentWaveDescriptor {
5876 flags: 1,
5877 ..wave(0, 1)
5878 }],
5879 1,
5880 )
5881 .is_err());
5882 assert!(super::validate_wave_partition(
5883 &[super::ResidentWaveDescriptor {
5884 reserved: 1,
5885 ..wave(0, 1)
5886 }],
5887 1,
5888 )
5889 .is_err());
5890 }
5891
5892 #[test]
5893 fn scalar_envelope_is_symbol_u32_and_u64_only() {
5894 assert_eq!(
5895 super::resident_schedule_scalar_width(ScalarType::Symbol).expect("symbol"),
5896 4
5897 );
5898 assert_eq!(
5899 super::resident_schedule_scalar_width(ScalarType::U32).expect("u32"),
5900 4
5901 );
5902 assert_eq!(
5903 super::resident_schedule_scalar_width(ScalarType::U64).expect("u64"),
5904 8
5905 );
5906 for unsupported in [
5907 ScalarType::I32,
5908 ScalarType::I64,
5909 ScalarType::F32,
5910 ScalarType::F64,
5911 ScalarType::Bool,
5912 ] {
5913 assert!(
5914 super::resident_schedule_scalar_width(unsupported).is_err(),
5915 "{unsupported:?} reached the unsigned scheduler kernel"
5916 );
5917 }
5918 }
5919
5920 #[test]
5921 fn device_storage_ranges_reject_partial_overlap() {
5922 assert!(super::device_ranges_overlap(0x1000, 16, 0x1008, 16));
5923 assert!(super::device_ranges_overlap(0x1008, 16, 0x1000, 16));
5924 assert!(!super::device_ranges_overlap(0x1000, 8, 0x1008, 8));
5925 assert!(!super::device_ranges_overlap(0x1000, 0, 0x1000, 8));
5926 }
5927
5928 #[test]
5929 fn preparation_rejects_filter_column_outside_input_schema() {
5930 let Some(provider) = provider() else { return };
5931 let relation_schema = schema("value", &[ScalarType::U32]);
5932 let input = buffer(&provider, relation_schema.clone(), &[vec![7]]);
5933 let mut output = buffer(&provider, relation_schema, &[vec![0]]);
5934 assert_eq!(output.cached_row_count(), Some(1));
5935 let relations = vec![
5936 super::ResidentScheduleRelation::source(&input, 1).expect("source relation"),
5937 super::ResidentScheduleRelation::output(&mut output, 2),
5938 ];
5939 let operations = [super::ResidentOpDescriptor {
5940 kind: super::OP_FILTER,
5941 op_id: 401,
5942 out: 1,
5943 in0: 0,
5944 in1: 0,
5945 in0_generation: 1,
5946 in1_generation: 0,
5947 out_generation: 2,
5948 aux_count: 1,
5949 ..Default::default()
5950 }];
5951 let comparisons = [super::ResidentFilterComparisonDescriptor::column_constant(
5952 1, 0, 4, 7,
5953 )];
5954 let wave = super::ResidentWaveDescriptor {
5955 op_count: 1,
5956 ..Default::default()
5957 };
5958 let region = super::ResidentRegionDescriptor {
5959 wave_count: 1,
5960 iteration_limit: 1,
5961 op_id: 400,
5962 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5963 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
5964 slot_count: 2,
5965 ..Default::default()
5966 };
5967
5968 assert_eq!(
5969 schedule_kernel_error(provider.prepare_resident_schedule(
5970 relations,
5971 &operations,
5972 &[wave],
5973 &[region],
5974 &comparisons,
5975 &[],
5976 &[],
5977 )),
5978 "resident schedule descriptor column is invalid"
5979 );
5980 assert_eq!(output.cached_row_count(), Some(1));
5981 }
5982
5983 #[test]
5984 fn preparation_rejects_non_nullary_unit_without_invalidating_output() {
5985 let Some(provider) = provider() else { return };
5986 let relation_schema = schema("not_unit", &[ScalarType::U32]);
5987 let mut output = buffer(&provider, relation_schema, &[vec![17]]);
5988 let relations = vec![super::ResidentScheduleRelation::output(&mut output, 2)];
5989 let operation = super::ResidentOpDescriptor::unit(451, 0, 2);
5990 let wave = super::ResidentWaveDescriptor {
5991 op_count: 1,
5992 ..Default::default()
5993 };
5994 let region = super::ResidentRegionDescriptor {
5995 wave_count: 1,
5996 iteration_limit: 1,
5997 op_id: 450,
5998 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
5999 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6000 slot_count: 1,
6001 ..Default::default()
6002 };
6003
6004 assert_eq!(
6005 schedule_kernel_error(provider.prepare_resident_schedule(
6006 relations,
6007 &[operation],
6008 &[wave],
6009 &[region],
6010 &[],
6011 &[],
6012 &[0],
6013 )),
6014 "resident schedule unit 451 has nonzero operands or invalid output"
6015 );
6016 assert_eq!(output.cached_row_count(), Some(1));
6017 }
6018
6019 #[test]
6020 fn real_cuda_unit_and_scan_execute_complete_leaf_semantics() {
6021 let Some(provider) = provider() else { return };
6022 let unit_schema = Schema::new(Vec::new());
6023 let mut unit_output = provider
6024 .prepare_resident_relation(unit_schema.clone(), 1)
6025 .expect("unit output")
6026 .into_buffer();
6027 let unit_relations = vec![super::ResidentScheduleRelation::output(&mut unit_output, 7)];
6028 let unit_op = super::ResidentOpDescriptor::unit(461, 0, 7);
6029 let wave = super::ResidentWaveDescriptor {
6030 op_count: 1,
6031 ..Default::default()
6032 };
6033 let region = super::ResidentRegionDescriptor {
6034 wave_count: 1,
6035 iteration_limit: 1,
6036 op_id: 460,
6037 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6038 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6039 slot_count: 1,
6040 ..Default::default()
6041 };
6042 let unit_schedule = provider
6043 .prepare_resident_schedule(
6044 unit_relations,
6045 &[unit_op],
6046 &[wave],
6047 &[region],
6048 &[],
6049 &[],
6050 &[0],
6051 )
6052 .expect("prepare Unit schedule");
6053 let unit_stream = provider
6054 .device()
6055 .inner()
6056 .stream()
6057 .context()
6058 .new_stream()
6059 .expect("Unit stream");
6060 let mut unit_graph = provider
6061 .capture_resident_schedule(unit_schedule, 0, Arc::clone(&unit_stream))
6062 .expect("capture Unit schedule");
6063 unit_graph.launch().expect("launch Unit schedule");
6064 let unit_receipt = unit_graph
6065 .synchronize_and_observe()
6066 .expect("observe Unit schedule");
6067 assert_eq!(
6068 unit_receipt.status.code,
6069 ResidentTerminalCode::Success as u32
6070 );
6071 assert_eq!(unit_receipt.counts, vec![1]);
6072 drop(unit_graph);
6073 assert_eq!(unit_output.cached_row_count(), Some(1));
6074
6075 let scan_schema = schema("scan", &[ScalarType::U32]);
6076 let scan_source = buffer(&provider, scan_schema, &[vec![3, 5, 8]]);
6077 let scan_relations =
6078 vec![super::ResidentScheduleRelation::source(&scan_source, 9).expect("Scan source")];
6079 let scan_op = super::ResidentOpDescriptor::scan(471, 0, 9);
6080 let scan_schedule = provider
6081 .prepare_resident_schedule(
6082 scan_relations,
6083 &[scan_op],
6084 &[wave],
6085 &[super::ResidentRegionDescriptor {
6086 op_id: 470,
6087 ..region
6088 }],
6089 &[],
6090 &[],
6091 &[0],
6092 )
6093 .expect("prepare Scan schedule");
6094 let scan_stream = provider
6095 .device()
6096 .inner()
6097 .stream()
6098 .context()
6099 .new_stream()
6100 .expect("Scan stream");
6101 let mut scan_graph = provider
6102 .capture_resident_schedule(scan_schedule, 0, scan_stream)
6103 .expect("capture Scan schedule");
6104 scan_graph.launch().expect("launch Scan schedule");
6105 let scan_receipt = scan_graph
6106 .synchronize_and_observe()
6107 .expect("observe Scan schedule");
6108 assert_eq!(
6109 scan_receipt.status.code,
6110 ResidentTerminalCode::Success as u32
6111 );
6112 assert_eq!(scan_receipt.counts, vec![3]);
6113 assert_eq!(scan_source.cached_row_count(), Some(3));
6114 }
6115
6116 #[test]
6117 fn real_cuda_unit_capacity_zero_reports_exact_overflow_without_storage() {
6118 let Some(provider) = provider() else { return };
6119 let mut output = provider
6120 .prepare_resident_relation(Schema::new(Vec::new()), 0)
6121 .expect("zero-capacity Unit output")
6122 .into_buffer();
6123 let relations = vec![super::ResidentScheduleRelation::output(&mut output, 3)];
6124 let operation = super::ResidentOpDescriptor::unit(481, 0, 3);
6125 let wave = super::ResidentWaveDescriptor {
6126 op_count: 1,
6127 ..Default::default()
6128 };
6129 let region = super::ResidentRegionDescriptor {
6130 wave_count: 1,
6131 iteration_limit: 1,
6132 op_id: 480,
6133 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6134 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6135 slot_count: 1,
6136 ..Default::default()
6137 };
6138 let schedule = provider
6139 .prepare_resident_schedule(relations, &[operation], &[wave], &[region], &[], &[], &[0])
6140 .expect("prepare zero-capacity Unit schedule");
6141 let stream = provider
6142 .device()
6143 .inner()
6144 .stream()
6145 .context()
6146 .new_stream()
6147 .expect("zero-capacity Unit stream");
6148 let mut graph = provider
6149 .capture_resident_schedule(schedule, 0, stream)
6150 .expect("capture zero-capacity Unit schedule");
6151 graph.launch().expect("launch zero-capacity Unit schedule");
6152 let receipt = graph
6153 .synchronize_and_observe()
6154 .expect("observe zero-capacity Unit schedule");
6155 assert_eq!(
6156 receipt.status.code,
6157 ResidentTerminalCode::CapacityOverflow as u32
6158 );
6159 assert_eq!(receipt.status.op_id, 481);
6160 assert_eq!(
6161 receipt.status.resource_code,
6162 ResidentResourceCode::OutputRows as u32
6163 );
6164 assert_eq!(receipt.status.required, 1);
6165 assert_eq!(receipt.status.capacity, 0);
6166 assert_eq!(receipt.counts, vec![0]);
6167 }
6168
6169 #[test]
6170 fn preparation_rejects_same_slot_input_output_alias() {
6171 let Some(provider) = provider() else { return };
6172 let input = buffer(
6173 &provider,
6174 schema("alias", &[ScalarType::U32]),
6175 &[vec![1, 2]],
6176 );
6177 let relations =
6178 vec![super::ResidentScheduleRelation::source(&input, 7).expect("source relation")];
6179 let operation = super::ResidentOpDescriptor {
6180 kind: super::OP_FILTER,
6181 op_id: 402,
6182 out: 0,
6183 in0: 0,
6184 in1: 0,
6185 in0_generation: 7,
6186 out_generation: 7,
6187 ..Default::default()
6188 };
6189 let wave = super::ResidentWaveDescriptor {
6190 first_op: 0,
6191 op_count: 1,
6192 ..Default::default()
6193 };
6194 let region = super::ResidentRegionDescriptor {
6195 first_wave: 0,
6196 wave_count: 1,
6197 iteration_limit: 1,
6198 op_id: 402,
6199 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6200 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6201 first_slot: 0,
6202 slot_count: 1,
6203 generation_offset: 0,
6204 };
6205
6206 let error = match provider.prepare_resident_schedule(
6207 relations,
6208 &[operation],
6209 &[wave],
6210 &[region],
6211 &[],
6212 &[],
6213 &[],
6214 ) {
6215 Err(error) => error,
6216 Ok(_) => panic!("same-slot input/output alias reached device preparation"),
6217 };
6218 assert!(error.to_string().contains("aliases"));
6219 assert_eq!(input.cached_row_count(), Some(2));
6220 }
6221
6222 #[test]
6223 fn preparation_rejects_cross_slot_shared_runtime_allocation_alias() {
6224 let Some(provider) = runtime_provider() else {
6225 return;
6226 };
6227 let relation_schema = schema("shared_storage", &[ScalarType::U32]);
6228 let mut source = buffer(&provider, relation_schema.clone(), &[vec![7]]);
6229 let mut output = buffer(&provider, relation_schema, &[vec![9]]);
6230 let shared = Arc::new(
6231 provider
6232 .memory
6233 .alloc::<u8>(4)
6234 .expect("shared runtime allocation"),
6235 );
6236 assert!(
6237 shared.runtime_block().is_some(),
6238 "alias witness requires runtime block identity and generation"
6239 );
6240 let stream = Arc::clone(provider.device().inner().stream());
6241 let source_tensor = unsafe { DlpackManagedTensor::from_raw(std::ptr::null_mut()) };
6242 let output_tensor = unsafe { DlpackManagedTensor::from_raw(std::ptr::null_mut()) };
6243 source.columns[0] =
6244 CudaColumn::dlpack_xlog_owned(Arc::clone(&shared), Arc::clone(&stream), source_tensor);
6245 output.columns[0] = CudaColumn::dlpack_xlog_owned(shared, stream, output_tensor);
6246 let relations = vec![
6247 super::ResidentScheduleRelation::source(&source, 1).expect("shared source"),
6248 super::ResidentScheduleRelation::output(&mut output, 2),
6249 ];
6250 let wave = super::ResidentWaveDescriptor {
6251 first_op: 0,
6252 op_count: 0,
6253 ..Default::default()
6254 };
6255 let region = super::ResidentRegionDescriptor {
6256 first_wave: 0,
6257 wave_count: 1,
6258 iteration_limit: 1,
6259 op_id: 405,
6260 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6261 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6262 first_slot: 0,
6263 slot_count: 2,
6264 generation_offset: 0,
6265 };
6266 let error = match provider.prepare_resident_schedule(
6267 relations,
6268 &[],
6269 &[wave],
6270 &[region],
6271 &[],
6272 &[],
6273 &[],
6274 ) {
6275 Err(error) => error,
6276 Ok(_) => panic!("shared runtime allocation reached scheduler allocation"),
6277 };
6278 assert!(error.to_string().contains("aliases storage"));
6279 assert_eq!(output.cached_row_count(), Some(1));
6280 }
6281
6282 #[test]
6283 fn preparation_rejects_cross_slot_overlapping_raw_views() {
6284 let Some(provider) = provider() else { return };
6285 let relation_schema = schema("overlap", &[ScalarType::U32]);
6286 let mut source = buffer(&provider, relation_schema.clone(), &[vec![7]]);
6287 let mut output = buffer(&provider, relation_schema, &[vec![9]]);
6288 let backing = provider
6289 .memory
6290 .alloc::<u8>(8)
6291 .expect("overlap backing allocation");
6292 let base = backing.device_ptr_value();
6293 let stream = Arc::clone(provider.device().inner().stream());
6294 source.columns[0] = CudaColumn::dlpack(base, 4, Arc::clone(&stream), unsafe {
6295 DlpackManagedTensor::from_raw(std::ptr::null_mut())
6296 });
6297 output.columns[0] = CudaColumn::dlpack(base + 2, 4, stream, unsafe {
6298 DlpackManagedTensor::from_raw(std::ptr::null_mut())
6299 });
6300 let relations = vec![
6301 super::ResidentScheduleRelation::source(&source, 1).expect("overlap source"),
6302 super::ResidentScheduleRelation::output(&mut output, 2),
6303 ];
6304 let region = super::ResidentRegionDescriptor {
6305 iteration_limit: 1,
6306 op_id: 406,
6307 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6308 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6309 first_slot: 0,
6310 slot_count: 2,
6311 ..Default::default()
6312 };
6313 let error =
6314 match provider.prepare_resident_schedule(relations, &[], &[], &[region], &[], &[], &[])
6315 {
6316 Err(error) => error,
6317 Ok(_) => panic!("overlapping raw views reached scheduler allocation"),
6318 };
6319 assert!(error.to_string().contains("aliases storage"));
6320 assert_eq!(output.cached_row_count(), Some(1));
6321 drop(backing);
6322 }
6323
6324 #[test]
6325 fn output_count_finalization_is_all_or_nothing() {
6326 let Some(provider) = provider() else { return };
6327 let relation_schema = schema("receipt", &[ScalarType::U32]);
6328 let mut first = buffer(&provider, relation_schema.clone(), &[vec![1]]);
6329 let mut second = buffer(&provider, relation_schema, &[vec![2]]);
6330 let mut relations = vec![
6331 super::ResidentScheduleRelation::output(&mut first, 1),
6332 super::ResidentScheduleRelation::output(&mut second, 2),
6333 ];
6334 for relation in &mut relations {
6335 relation.invalidate_output_metadata();
6336 }
6337 let error = super::finalize_schedule_output_counts(&provider, &relations, &[0, 1], &[1, 2])
6338 .expect_err("second count exceeds capacity");
6339 assert!(error.to_string().contains("exceeds buffer capacity"));
6340 drop(relations);
6341 assert_eq!(first.cached_row_count(), None);
6342 assert_eq!(second.cached_row_count(), None);
6343 }
6344
6345 #[test]
6346 fn preparation_rejects_relation_from_foreign_provider() {
6347 let Some(foreign_provider) = provider() else {
6348 return;
6349 };
6350 let Some(provider) = provider() else { return };
6351 let relation_schema = schema("context", &[ScalarType::U32]);
6352 let input = buffer(&foreign_provider, relation_schema.clone(), &[vec![9]]);
6353 let mut output = buffer(&provider, relation_schema, &[vec![0]]);
6354 let relations = vec![
6355 super::ResidentScheduleRelation::source(&input, 1).expect("foreign source"),
6356 super::ResidentScheduleRelation::output(&mut output, 2),
6357 ];
6358 let operation = super::ResidentOpDescriptor {
6359 kind: super::OP_FILTER,
6360 op_id: 403,
6361 out: 1,
6362 in0: 0,
6363 in0_generation: 1,
6364 out_generation: 2,
6365 ..Default::default()
6366 };
6367
6368 let error = match provider.prepare_resident_schedule(
6369 relations,
6370 &[operation],
6371 &[],
6372 &[],
6373 &[],
6374 &[],
6375 &[],
6376 ) {
6377 Err(error) => error,
6378 Ok(_) => panic!("foreign relation reached scheduler allocation"),
6379 };
6380 assert!(error.to_string().contains("foreign"));
6381 assert_eq!(output.cached_row_count(), Some(1));
6382 }
6383
6384 #[test]
6385 fn preparation_rejects_ignored_flags_iteration_limits_and_slot_scopes() {
6386 let Some(provider) = provider() else { return };
6387 let base_wave = super::ResidentWaveDescriptor {
6388 first_op: 0,
6389 op_count: 0,
6390 ..Default::default()
6391 };
6392 let base_region = super::ResidentRegionDescriptor {
6393 first_wave: 0,
6394 wave_count: 1,
6395 iteration_limit: 1,
6396 op_id: 410,
6397 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6398 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6399 ..Default::default()
6400 };
6401 let invalid_wave = super::ResidentWaveDescriptor {
6402 flags: 1,
6403 ..base_wave
6404 };
6405 assert_eq!(
6406 schedule_kernel_error(provider.prepare_resident_schedule(
6407 Vec::new(),
6408 &[],
6409 &[invalid_wave],
6410 &[base_region],
6411 &[],
6412 &[],
6413 &[],
6414 )),
6415 "resident schedule waves must exactly partition operations"
6416 );
6417 let invalid_wave_reserved = super::ResidentWaveDescriptor {
6418 reserved: 1,
6419 ..base_wave
6420 };
6421 assert_eq!(
6422 schedule_kernel_error(provider.prepare_resident_schedule(
6423 Vec::new(),
6424 &[],
6425 &[invalid_wave_reserved],
6426 &[base_region],
6427 &[],
6428 &[],
6429 &[],
6430 )),
6431 "resident schedule waves must exactly partition operations"
6432 );
6433 let invalid_region = super::ResidentRegionDescriptor {
6434 flags: base_region.flags | (1 << 31),
6435 ..base_region
6436 };
6437 assert_eq!(
6438 schedule_kernel_error(provider.prepare_resident_schedule(
6439 Vec::new(),
6440 &[],
6441 &[base_wave],
6442 &[invalid_region],
6443 &[],
6444 &[],
6445 &[],
6446 )),
6447 "resident schedule region range or reserved field is invalid"
6448 );
6449 let invalid_region_reserved = super::ResidentRegionDescriptor {
6450 generation_offset: 1,
6451 ..base_region
6452 };
6453 assert_eq!(
6454 schedule_kernel_error(provider.prepare_resident_schedule(
6455 Vec::new(),
6456 &[],
6457 &[base_wave],
6458 &[invalid_region_reserved],
6459 &[],
6460 &[],
6461 &[],
6462 )),
6463 "resident schedule generation baselines are not contiguous"
6464 );
6465 let invalid_limit = super::ResidentRegionDescriptor {
6466 iteration_limit: 2,
6467 ..base_region
6468 };
6469 assert_eq!(
6470 schedule_kernel_error(provider.prepare_resident_schedule(
6471 Vec::new(),
6472 &[],
6473 &[base_wave],
6474 &[invalid_limit],
6475 &[],
6476 &[],
6477 &[],
6478 )),
6479 "resident schedule region control flags are invalid"
6480 );
6481 let zero_limit = super::ResidentRegionDescriptor {
6482 iteration_limit: 0,
6483 ..base_region
6484 };
6485 assert_eq!(
6486 schedule_kernel_error(provider.prepare_resident_schedule(
6487 Vec::new(),
6488 &[],
6489 &[base_wave],
6490 &[zero_limit],
6491 &[],
6492 &[],
6493 &[],
6494 )),
6495 "resident schedule region control flags are invalid"
6496 );
6497 let mismatched_limits = [
6498 super::ResidentRegionDescriptor {
6499 iteration_limit: 2,
6500 op_id: 420,
6501 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6502 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
6503 ..Default::default()
6504 },
6505 super::ResidentRegionDescriptor {
6506 iteration_limit: 3,
6507 op_id: 420,
6508 flags: super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
6509 ..Default::default()
6510 },
6511 super::ResidentRegionDescriptor {
6512 iteration_limit: 1,
6513 op_id: 421,
6514 flags: super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6515 ..Default::default()
6516 },
6517 ];
6518 assert_eq!(
6519 schedule_kernel_error(provider.prepare_resident_schedule(
6520 Vec::new(),
6521 &[],
6522 &[],
6523 &mismatched_limits,
6524 &[],
6525 &[],
6526 &[],
6527 )),
6528 "resident schedule SCC begin does not match its recursive body"
6529 );
6530
6531 let relation_schema = schema("scope", &[ScalarType::U32]);
6532 let input = buffer(&provider, relation_schema.clone(), &[vec![1]]);
6533 let mut output = buffer(&provider, relation_schema, &[vec![0]]);
6534 let relations = vec![
6535 super::ResidentScheduleRelation::source(&input, 1).expect("scope source"),
6536 super::ResidentScheduleRelation::output(&mut output, 2),
6537 ];
6538 let operation = super::ResidentOpDescriptor {
6539 kind: super::OP_FILTER,
6540 op_id: 411,
6541 out: 1,
6542 in0: 0,
6543 in0_generation: 1,
6544 out_generation: 2,
6545 ..Default::default()
6546 };
6547 let scoped_wave = super::ResidentWaveDescriptor {
6548 op_count: 1,
6549 ..base_wave
6550 };
6551 let too_narrow = super::ResidentRegionDescriptor {
6552 first_slot: 0,
6553 slot_count: 1,
6554 ..base_region
6555 };
6556 assert_eq!(
6557 schedule_kernel_error(provider.prepare_resident_schedule(
6558 relations,
6559 &[operation],
6560 &[scoped_wave],
6561 &[too_narrow],
6562 &[],
6563 &[],
6564 &[],
6565 )),
6566 "resident schedule initialization must cover every relation slot"
6567 );
6568 assert_eq!(output.cached_row_count(), Some(1));
6569
6570 let relations = vec![
6571 super::ResidentScheduleRelation::source(&input, 1).expect("flag source"),
6572 super::ResidentScheduleRelation::output(&mut output, 2),
6573 ];
6574 let invalid_operation_flags = super::ResidentOpDescriptor {
6575 flags: 4,
6576 ..operation
6577 };
6578 assert_eq!(
6579 schedule_kernel_error(provider.prepare_resident_schedule(
6580 relations,
6581 &[invalid_operation_flags],
6582 &[scoped_wave],
6583 &[super::ResidentRegionDescriptor {
6584 first_slot: 0,
6585 slot_count: 2,
6586 ..base_region
6587 }],
6588 &[],
6589 &[],
6590 &[],
6591 )),
6592 "resident schedule operation 411 has an unsupported kind, flag, or payload"
6593 );
6594 assert_eq!(output.cached_row_count(), Some(1));
6595
6596 let relations = vec![
6597 super::ResidentScheduleRelation::source(&input, 1).expect("reserved source"),
6598 super::ResidentScheduleRelation::output(&mut output, 2),
6599 ];
6600 let invalid_operation_reserved = super::ResidentOpDescriptor {
6601 reserved: 1,
6602 ..operation
6603 };
6604 assert_eq!(
6605 schedule_kernel_error(provider.prepare_resident_schedule(
6606 relations,
6607 &[invalid_operation_reserved],
6608 &[scoped_wave],
6609 &[super::ResidentRegionDescriptor {
6610 first_slot: 0,
6611 slot_count: 2,
6612 ..base_region
6613 }],
6614 &[],
6615 &[],
6616 &[],
6617 )),
6618 "resident schedule operation 411 has an unsupported kind, flag, or payload"
6619 );
6620 assert_eq!(output.cached_row_count(), Some(1));
6621
6622 let relations = vec![
6623 super::ResidentScheduleRelation::source(&input, 1).expect("overflowing scope source"),
6624 super::ResidentScheduleRelation::output(&mut output, 2),
6625 ];
6626 let overflowing_scope = super::ResidentRegionDescriptor {
6627 first_slot: u32::MAX,
6628 slot_count: 2,
6629 ..base_region
6630 };
6631 assert_eq!(
6632 schedule_kernel_error(provider.prepare_resident_schedule(
6633 relations,
6634 &[operation],
6635 &[scoped_wave],
6636 &[overflowing_scope],
6637 &[],
6638 &[],
6639 &[],
6640 )),
6641 "resident schedule generation baseline slot scope is invalid"
6642 );
6643 assert_eq!(output.cached_row_count(), Some(1));
6644 }
6645
6646 #[test]
6647 fn preparation_accepts_explicit_multihead_recursive_novelty_contract() {
6648 let Some(provider) = provider() else { return };
6649 let relation_schema = schema("recursive", &[ScalarType::U32]);
6650 let changed = buffer(&provider, relation_schema.clone(), &[vec![1]]);
6651 let empty = buffer(&provider, relation_schema.clone(), &[Vec::new()]);
6652 let stable_left = buffer(&provider, relation_schema.clone(), &[vec![7]]);
6653 let stable_right = buffer(&provider, relation_schema.clone(), &[vec![7]]);
6654 let mut first_novel = provider
6655 .prepare_resident_relation(relation_schema.clone(), 1)
6656 .expect("first novelty output")
6657 .into_buffer();
6658 let mut second_novel = provider
6659 .prepare_resident_relation(relation_schema, 1)
6660 .expect("second novelty output")
6661 .into_buffer();
6662 let relations = vec![
6663 super::ResidentScheduleRelation::source(&changed, 1).expect("changed source"),
6664 super::ResidentScheduleRelation::source(&empty, 2).expect("empty source"),
6665 super::ResidentScheduleRelation::source(&stable_left, 3).expect("stable source"),
6666 super::ResidentScheduleRelation::source(&stable_right, 4).expect("stable source"),
6667 super::ResidentScheduleRelation::output(&mut first_novel, 5),
6668 super::ResidentScheduleRelation::output(&mut second_novel, 6),
6669 ];
6670 let operations = [
6671 super::ResidentOpDescriptor {
6672 kind: super::OP_DIFF,
6673 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY,
6674 op_id: 501,
6675 out: 4,
6676 in0: 0,
6677 in1: 1,
6678 in0_generation: 1,
6679 in1_generation: 2,
6680 out_generation: 5,
6681 ..Default::default()
6682 },
6683 super::ResidentOpDescriptor {
6684 kind: super::OP_DIFF,
6685 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY,
6686 op_id: 502,
6687 out: 5,
6688 in0: 2,
6689 in1: 3,
6690 in0_generation: 3,
6691 in1_generation: 4,
6692 out_generation: 6,
6693 ..Default::default()
6694 },
6695 ];
6696 let wave = super::ResidentWaveDescriptor {
6697 first_op: 0,
6698 op_count: 2,
6699 ..Default::default()
6700 };
6701 let regions = [
6702 super::ResidentRegionDescriptor {
6703 first_wave: 0,
6704 wave_count: 0,
6705 iteration_limit: 1,
6706 op_id: 500,
6707 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6708 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
6709 first_slot: 0,
6710 slot_count: 6,
6711 generation_offset: 0,
6712 },
6713 super::ResidentRegionDescriptor {
6714 first_wave: 0,
6715 wave_count: 1,
6716 iteration_limit: 1,
6717 op_id: 500,
6718 flags: super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
6719 first_slot: 0,
6720 slot_count: 6,
6721 generation_offset: 0,
6722 },
6723 super::ResidentRegionDescriptor {
6724 first_wave: 1,
6725 wave_count: 0,
6726 iteration_limit: 1,
6727 op_id: 503,
6728 flags: super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6729 first_slot: 0,
6730 slot_count: 6,
6731 generation_offset: 0,
6732 },
6733 ];
6734
6735 let schedule = provider
6736 .prepare_resident_schedule(relations, &operations, &[wave], ®ions, &[], &[], &[4, 5])
6737 .expect("explicit recursive schedule contract");
6738 let stream = provider
6739 .device()
6740 .inner()
6741 .stream()
6742 .context()
6743 .new_stream()
6744 .expect("recursive schedule stream");
6745 let mut graph = provider
6746 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
6747 .expect("capture recursive schedule");
6748 let nodes = graph.nodes().expect("recursive parent inventory");
6749 assert_eq!(nodes.len(), 3);
6750 assert_eq!(nodes[0].kind, CudaGraphNodeKind::Kernel);
6751 assert_eq!(nodes[1].kind, CudaGraphNodeKind::Conditional);
6752 assert_eq!(nodes[2].kind, CudaGraphNodeKind::Kernel);
6753 graph.launch().expect("launch recursive schedule");
6754 let receipt = graph
6755 .synchronize_and_observe()
6756 .expect("recursive schedule receipt");
6757 assert_eq!(
6758 receipt.status.code,
6759 ResidentTerminalCode::IterationLimit as u32
6760 );
6761 assert_eq!(receipt.status.op_id, 500);
6762 assert_eq!(receipt.status.iterations, 1);
6763 assert_eq!(receipt.status.limit, 1);
6764 assert_eq!(receipt.changed, 1);
6765 assert_eq!(receipt.counts, vec![0, 0]);
6766 }
6767
6768 #[test]
6769 fn real_cuda_recursive_zero_convergence_and_iteration_limit_are_exact() {
6770 let Some(provider) = provider() else { return };
6771 let expected_inventory = vec![
6772 CudaGraphNodeKind::Kernel,
6773 CudaGraphNodeKind::Conditional,
6774 CudaGraphNodeKind::Kernel,
6775 ];
6776
6777 let (zero_limit, zero_limit_nodes) = run_single_recursive_diff(&provider, &[1], &[], 0);
6778 assert_eq!(zero_limit_nodes, expected_inventory);
6779 assert_eq!(
6780 zero_limit.status.code,
6781 ResidentTerminalCode::IterationLimit as u32
6782 );
6783 assert_eq!(zero_limit.status.op_id, 600);
6784 assert_eq!(zero_limit.status.iterations, 0);
6785 assert_eq!(zero_limit.status.limit, 0);
6786 assert_eq!(zero_limit.changed, 0);
6787 assert_eq!(zero_limit.counts, vec![0]);
6788
6789 let (converged, converged_nodes) = run_single_recursive_diff(&provider, &[7], &[7], 3);
6790 assert_eq!(converged_nodes, expected_inventory);
6791 assert_eq!(converged.status.code, ResidentTerminalCode::Success as u32);
6792 assert_eq!(converged.status.op_id, 602);
6793 assert_eq!(converged.status.iterations, 1);
6794 assert_eq!(converged.status.limit, 0);
6795 assert_eq!(converged.changed, 0);
6796 assert_eq!(converged.counts, vec![0]);
6797
6798 let (limited, limited_nodes) = run_single_recursive_diff(&provider, &[9], &[], 3);
6799 assert_eq!(limited_nodes, expected_inventory);
6800 assert_eq!(
6801 limited.status.code,
6802 ResidentTerminalCode::IterationLimit as u32
6803 );
6804 assert_eq!(limited.status.op_id, 600);
6805 assert_eq!(limited.status.iterations, 3);
6806 assert_eq!(limited.status.limit, 3);
6807 assert_eq!(limited.changed, 1);
6808 assert_eq!(limited.counts, vec![0]);
6809 }
6810
6811 #[test]
6812 fn real_cuda_two_serial_sccs_preserve_sticky_status_and_aggregate_iterations() {
6813 let Some(provider) = provider() else { return };
6814 let expected_inventory = vec![
6815 CudaGraphNodeKind::Kernel,
6816 CudaGraphNodeKind::Conditional,
6817 CudaGraphNodeKind::Kernel,
6818 CudaGraphNodeKind::Conditional,
6819 CudaGraphNodeKind::Kernel,
6820 ];
6821
6822 let (success, success_nodes, _) = run_two_scc_diff(&provider, 4, 4, false, false);
6823 assert_eq!(success_nodes, expected_inventory);
6824 assert_eq!(success.status.code, ResidentTerminalCode::Success as u32);
6825 assert_eq!(success.status.op_id, 703);
6826 assert_eq!(success.status.iterations, 2);
6827 assert_eq!(success.status.limit, 0);
6828 assert_eq!(success.counts, vec![0, 0]);
6829
6830 let (first_zero, first_zero_nodes, first_zero_storage) =
6831 run_two_scc_diff(&provider, 0, 4, true, true);
6832 assert_eq!(first_zero_nodes, expected_inventory);
6833 assert_eq!(
6834 first_zero.status.code,
6835 ResidentTerminalCode::IterationLimit as u32
6836 );
6837 assert_eq!(first_zero.status.op_id, 701);
6838 assert_eq!(first_zero.status.iterations, 0);
6839 assert_eq!(first_zero.status.limit, 0);
6840 assert_eq!(first_zero.counts, vec![0, 0]);
6841 assert_eq!(first_zero_storage, [0x1111_1111, 0x2222_2222]);
6842
6843 let (second_zero, second_zero_nodes, second_zero_storage) =
6844 run_two_scc_diff(&provider, 4, 0, false, true);
6845 assert_eq!(second_zero_nodes, expected_inventory);
6846 assert_eq!(
6847 second_zero.status.code,
6848 ResidentTerminalCode::IterationLimit as u32
6849 );
6850 assert_eq!(second_zero.status.op_id, 702);
6851 assert_eq!(second_zero.status.iterations, 1);
6852 assert_eq!(second_zero.status.limit, 0);
6853 assert_eq!(second_zero.counts, vec![0, 0]);
6854 assert_eq!(second_zero_storage, [0x1111_1111, 0x2222_2222]);
6855 }
6856
6857 #[test]
6858 fn real_cuda_recursive_overflow_is_sticky_and_stops_downstream_writes() {
6859 let Some(provider) = provider() else { return };
6860 let relation_schema = schema("recursive_overflow", &[ScalarType::U32]);
6861 let left = buffer(&provider, relation_schema.clone(), &[vec![1, 2]]);
6862 let right = buffer(&provider, relation_schema.clone(), &[Vec::new()]);
6863 let mut novelty = buffer(&provider, relation_schema.clone(), &[vec![0xaaaa_aaaa]]);
6864 let mut downstream = buffer(&provider, relation_schema, &[vec![0xbbbb_bbbb]]);
6865 let relations = vec![
6866 super::ResidentScheduleRelation::source(&left, 1).expect("left source"),
6867 super::ResidentScheduleRelation::source(&right, 2).expect("right source"),
6868 super::ResidentScheduleRelation::output(&mut novelty, 3),
6869 super::ResidentScheduleRelation::output(&mut downstream, 4),
6870 ];
6871 let operations = [
6872 super::ResidentOpDescriptor {
6873 kind: super::OP_DIFF,
6874 flags: super::RESIDENT_SCHEDULE_OP_MARK_NOVELTY,
6875 op_id: 731,
6876 out: 2,
6877 in0: 0,
6878 in1: 1,
6879 in0_generation: 1,
6880 in1_generation: 2,
6881 out_generation: 3,
6882 ..Default::default()
6883 },
6884 super::ResidentOpDescriptor {
6885 kind: super::OP_PROJECT,
6886 op_id: 732,
6887 out: 3,
6888 in0: 2,
6889 in0_generation: 3,
6890 out_generation: 4,
6891 aux_count: 1,
6892 ..Default::default()
6893 },
6894 ];
6895 let expression = super::ResidentProjectExpressionDescriptor::column(0, 4);
6896 let wave = super::ResidentWaveDescriptor {
6897 first_op: 0,
6898 op_count: 2,
6899 ..Default::default()
6900 };
6901 let regions = [
6902 super::ResidentRegionDescriptor {
6903 first_wave: 0,
6904 wave_count: 0,
6905 iteration_limit: 4,
6906 op_id: 730,
6907 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
6908 | super::RESIDENT_SCHEDULE_REGION_SCC_BEGIN,
6909 first_slot: 0,
6910 slot_count: 4,
6911 generation_offset: 0,
6912 },
6913 super::ResidentRegionDescriptor {
6914 first_wave: 0,
6915 wave_count: 1,
6916 iteration_limit: 4,
6917 op_id: 730,
6918 flags: super::RESIDENT_SCHEDULE_REGION_RECURSIVE,
6919 first_slot: 0,
6920 slot_count: 4,
6921 generation_offset: 0,
6922 },
6923 super::ResidentRegionDescriptor {
6924 first_wave: 1,
6925 wave_count: 0,
6926 iteration_limit: 1,
6927 op_id: 733,
6928 flags: super::RESIDENT_SCHEDULE_REGION_FINALIZE,
6929 first_slot: 0,
6930 slot_count: 4,
6931 generation_offset: 0,
6932 },
6933 ];
6934 let schedule = provider
6935 .prepare_resident_schedule(
6936 relations,
6937 &operations,
6938 &[wave],
6939 ®ions,
6940 &[],
6941 &[expression],
6942 &[2, 3],
6943 )
6944 .expect("prepare recursive overflow schedule");
6945 let stream = provider
6946 .device()
6947 .inner()
6948 .stream()
6949 .context()
6950 .new_stream()
6951 .expect("recursive overflow stream");
6952 let mut graph = provider
6953 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
6954 .expect("capture recursive overflow schedule");
6955 graph.launch().expect("launch recursive overflow schedule");
6956 let receipt = graph
6957 .synchronize_and_observe()
6958 .expect("recursive overflow receipt");
6959 assert_eq!(
6960 receipt.status.code,
6961 ResidentTerminalCode::CapacityOverflow as u32
6962 );
6963 assert_eq!(receipt.status.op_id, 731);
6964 assert_eq!(receipt.status.iterations, 0);
6965 assert_eq!(receipt.status.limit, 4);
6966 assert_eq!(receipt.status.required, 2);
6967 assert_eq!(receipt.status.capacity, 1);
6968 assert_eq!(receipt.changed, 0);
6969 assert_eq!(receipt.counts, vec![0, 0]);
6970 for (slot, expected) in [(2_usize, 0xaaaa_aaaa_u32), (3, 0xbbbb_bbbb)] {
6971 let raw: Vec<u8> = provider
6972 .device()
6973 .inner()
6974 .dtoh_sync_copy(
6975 graph
6976 .relation(slot)
6977 .expect("recursive output")
6978 .column(0)
6979 .expect("recursive output column"),
6980 )
6981 .expect("recursive output storage");
6982 assert_eq!(&raw[..4], &expected.to_le_bytes());
6983 }
6984 }
6985
6986 #[test]
6987 fn capture_rejects_stream_from_foreign_provider_context() {
6988 let Some(foreign_provider) = provider() else {
6989 return;
6990 };
6991 let Some(provider) = provider() else { return };
6992 let wave = super::ResidentWaveDescriptor {
6993 first_op: 0,
6994 op_count: 0,
6995 ..Default::default()
6996 };
6997 let region = super::ResidentRegionDescriptor {
6998 first_wave: 0,
6999 wave_count: 1,
7000 iteration_limit: 1,
7001 op_id: 420,
7002 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
7003 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
7004 ..Default::default()
7005 };
7006 let schedule = provider
7007 .prepare_resident_schedule(Vec::new(), &[], &[wave], &[region], &[], &[], &[])
7008 .expect("empty local schedule");
7009 let foreign_stream = foreign_provider
7010 .device()
7011 .inner()
7012 .stream()
7013 .context()
7014 .new_stream()
7015 .expect("foreign stream");
7016
7017 let error = match provider.capture_resident_schedule(schedule, 0, foreign_stream) {
7018 Err(error) => error,
7019 Ok(_) => panic!("foreign stream captured a local schedule"),
7020 };
7021 assert!(error.to_string().contains("foreign CUDA context"));
7022 }
7023
7024 #[test]
7025 fn real_cuda_selected_stream_cooperative_capture_has_one_kernel_node() {
7026 let Some(provider) = provider() else { return };
7027 let wave = super::ResidentWaveDescriptor {
7028 first_op: 0,
7029 op_count: 0,
7030 flags: 0,
7031 reserved: 0,
7032 };
7033 let region = super::ResidentRegionDescriptor {
7034 first_wave: 0,
7035 wave_count: 1,
7036 iteration_limit: 1,
7037 op_id: 91,
7038 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
7039 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
7040 first_slot: 0,
7041 slot_count: 0,
7042 generation_offset: 0,
7043 };
7044 let schedule = provider
7045 .prepare_resident_schedule(Vec::new(), &[], &[wave], &[region], &[], &[], &[])
7046 .expect("prepare empty resident schedule");
7047 let stream = provider
7048 .device()
7049 .inner()
7050 .stream()
7051 .context()
7052 .new_stream()
7053 .expect("non-default stream");
7054 assert_ne!(
7055 stream.cu_stream(),
7056 provider.device().inner().stream().cu_stream(),
7057 "feasibility gate must capture on a selected non-default stream"
7058 );
7059
7060 let mut graph = provider
7061 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
7062 .expect("capture cooperative resident schedule");
7063 assert_eq!(graph.node_count().expect("node count"), 1);
7064 let nodes = graph.nodes().expect("node inventory");
7065 assert_eq!(nodes.len(), 1);
7066 assert_eq!(nodes[0].kind, CudaGraphNodeKind::Kernel);
7067
7068 graph.launch().expect("launch resident schedule");
7069 let receipt = graph
7070 .synchronize_and_observe()
7071 .expect("resident schedule receipt");
7072 assert_eq!(receipt.status.code, ResidentTerminalCode::Success as u32);
7073 assert_eq!(receipt.status.op_id, 91);
7074 }
7075
7076 #[test]
7077 fn real_cuda_graph_lease_rejects_overlap_allows_replay_and_synchronizes_drop() {
7078 let Some(provider) = provider() else { return };
7079 let relation_schema = schema("graph_lease", &[ScalarType::U32]);
7080 let input = buffer(&provider, relation_schema.clone(), &[vec![1, 2]]);
7081 let mut output = buffer(&provider, relation_schema.clone(), &[vec![0, 0]]);
7082 let schedule = passthrough_schedule(&provider, &input, &mut output, 901);
7083 let stream = provider
7084 .device()
7085 .inner()
7086 .stream()
7087 .context()
7088 .new_stream()
7089 .expect("graph lease stream");
7090 let mut graph = provider
7091 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
7092 .expect("capture graph lease schedule");
7093
7094 assert_eq!(
7095 graph
7096 .relation(1)
7097 .expect("leased output before launch")
7098 .cached_row_count(),
7099 None
7100 );
7101 match graph.synchronize_and_observe() {
7102 Err(XlogError::Kernel(message)) => assert_eq!(
7103 message,
7104 "resident schedule has no in-flight launch to observe"
7105 ),
7106 Err(error) => panic!("unexpected pre-launch observation error: {error}"),
7107 Ok(_) => panic!("pre-launch observation unexpectedly succeeded"),
7108 }
7109
7110 graph.launch().expect("first graph lease launch");
7111 match graph.relation(1) {
7112 Err(XlogError::Kernel(message)) => assert_eq!(
7113 message,
7114 "resident schedule relation is unavailable while launch is in flight"
7115 ),
7116 Err(error) => panic!("unexpected in-flight relation error: {error}"),
7117 Ok(_) => panic!("in-flight relation access unexpectedly succeeded"),
7118 }
7119 match graph.launch() {
7120 Err(XlogError::Kernel(message)) => {
7121 assert_eq!(message, "resident schedule launch is already in flight")
7122 }
7123 Err(error) => panic!("unexpected overlapping launch error: {error}"),
7124 Ok(()) => panic!("overlapping graph launch unexpectedly succeeded"),
7125 }
7126 let first = graph
7127 .synchronize_and_observe()
7128 .expect("first graph lease receipt");
7129 assert_eq!(first.status.code, ResidentTerminalCode::Success as u32);
7130 assert_eq!(first.status.op_id, 902);
7131 assert_eq!(first.counts, vec![2]);
7132 assert_eq!(
7133 normalized_rows(&provider, graph.relation(1).expect("first replay output")),
7134 vec![vec![1], vec![2]]
7135 );
7136 match graph.synchronize_and_observe() {
7137 Err(XlogError::Kernel(message)) => assert_eq!(
7138 message,
7139 "resident schedule has no in-flight launch to observe"
7140 ),
7141 Err(error) => panic!("unexpected duplicate observation error: {error}"),
7142 Ok(_) => panic!("duplicate observation unexpectedly succeeded"),
7143 }
7144
7145 graph.launch().expect("replayed graph lease launch");
7146 let replay = graph
7147 .synchronize_and_observe()
7148 .expect("replayed graph lease receipt");
7149 assert_eq!(replay, first);
7150 drop(graph);
7151 assert_eq!(output.cached_row_count(), Some(2));
7152 assert!(!output.canonical_full_row_set_certified());
7153 assert_eq!(normalized_rows(&provider, &output), vec![vec![1], vec![2]]);
7154
7155 let mut dropped_output = buffer(&provider, relation_schema, &[vec![0, 0]]);
7156 let dropped_schedule = passthrough_schedule(&provider, &input, &mut dropped_output, 911);
7157 let dropped_stream = provider
7158 .device()
7159 .inner()
7160 .stream()
7161 .context()
7162 .new_stream()
7163 .expect("drop synchronization stream");
7164 let mut dropped_graph = provider
7165 .capture_resident_schedule(dropped_schedule, 0, dropped_stream)
7166 .expect("capture drop synchronization schedule");
7167 dropped_graph
7168 .launch()
7169 .expect("launch drop synchronization schedule");
7170 drop(dropped_graph);
7171 assert_eq!(dropped_output.cached_row_count(), None);
7172 assert!(!dropped_output.canonical_full_row_set_certified());
7173 assert_eq!(
7174 normalized_rows(&provider, &dropped_output),
7175 vec![vec![1], vec![2]]
7176 );
7177 }
7178
7179 #[test]
7180 fn capture_rejects_sibling_provider_on_the_same_cuda_context() {
7181 let Some(provider) = provider() else { return };
7182 let relation_schema = schema("provider_identity", &[ScalarType::U32]);
7183 let input = buffer(&provider, relation_schema.clone(), &[vec![1]]);
7184 let mut output = buffer(&provider, relation_schema, &[vec![0]]);
7185 let schedule = passthrough_schedule(&provider, &input, &mut output, 921);
7186 let sibling_memory = Arc::new(GpuMemoryManager::new(
7187 Arc::clone(provider.device()),
7188 MemoryBudget::with_limit(512 * 1024 * 1024),
7189 ));
7190 let sibling = CudaKernelProvider::from_loaded_device(
7191 Arc::clone(provider.device()),
7192 sibling_memory,
7193 None,
7194 );
7195 let stream = provider
7196 .device()
7197 .inner()
7198 .stream()
7199 .context()
7200 .new_stream()
7201 .expect("same-context identity stream");
7202
7203 match sibling.capture_resident_schedule(schedule, 0, stream) {
7204 Err(XlogError::Kernel(message)) => assert_eq!(
7205 message,
7206 "resident schedule belongs to a different CUDA kernel provider"
7207 ),
7208 Err(error) => panic!("unexpected provider identity error: {error}"),
7209 Ok(_) => panic!("sibling provider captured a foreign resident schedule"),
7210 }
7211 assert_eq!(output.cached_row_count(), None);
7212 }
7213
7214 #[test]
7215 fn real_cuda_schedule_receipt_uses_one_pinned_final_dtoh_per_observation() {
7216 let Some(provider) = provider() else { return };
7217 let relation_schema = schema("receipt_accounting", &[ScalarType::U32]);
7218 let input = buffer(&provider, relation_schema.clone(), &[vec![1, 2]]);
7219 let mut output = buffer(&provider, relation_schema, &[vec![0, 0]]);
7220 let schedule = passthrough_schedule(&provider, &input, &mut output, 931);
7221 let expected_bytes =
7222 size_of::<crate::provider::resident_relational::ResidentTerminalStatus>()
7223 + 2 * size_of::<u32>();
7224 let stream = provider
7225 .device()
7226 .inner()
7227 .stream()
7228 .context()
7229 .new_stream()
7230 .expect("receipt accounting stream");
7231 let mut graph = provider
7232 .capture_resident_schedule(schedule, 0, stream)
7233 .expect("capture receipt accounting schedule");
7234 provider.reset_host_transfer_stats();
7235 provider.reset_untracked_metadata_dtoh_count();
7236 provider.reset_final_observation_transfer_stats();
7237
7238 graph.launch().expect("launch receipt accounting schedule");
7239 assert_eq!(provider.host_transfer_stats().dtoh_calls, 0);
7240 assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
7241 assert_eq!(provider.final_observation_transfer_stats().dtoh_calls, 0);
7242 graph
7243 .synchronize_and_observe()
7244 .expect("first receipt accounting observation");
7245 assert_eq!(provider.host_transfer_stats().dtoh_calls, 0);
7246 assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
7247 let first = provider.final_observation_transfer_stats();
7248 assert_eq!(first.dtoh_calls, 1);
7249 assert_eq!(first.dtoh_bytes, expected_bytes as u64);
7250 assert_eq!(first.pinned_receipts, 1);
7251
7252 graph.launch().expect("replay receipt accounting schedule");
7253 graph
7254 .synchronize_and_observe()
7255 .expect("replay receipt accounting observation");
7256 assert_eq!(provider.host_transfer_stats().dtoh_calls, 0);
7257 assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
7258 let replay = provider.final_observation_transfer_stats();
7259 assert_eq!(replay.dtoh_calls, 2);
7260 assert_eq!(replay.dtoh_bytes, 2 * expected_bytes as u64);
7261 assert_eq!(replay.pinned_receipts, 2);
7262 }
7263
7264 #[test]
7265 fn real_cuda_compact_union_orders_non_power_of_two_rows_across_merge_tiles() {
7266 let Some(provider) = provider() else { return };
7267 let relation_schema = schema("ordered_union", &[ScalarType::U32]);
7268 let mut left: Vec<u64> = (0..1_537).rev().collect();
7269 left.extend([0, 512, 1_536]);
7270 let mut right: Vec<u64> = (768..2_305).rev().collect();
7271 right.extend([768, 1_536, 2_304]);
7272 let (receipt, rows) = compact_set_rows(
7273 &provider,
7274 relation_schema,
7275 &[left],
7276 &[right],
7277 super::OP_UNION,
7278 2_305,
7279 );
7280 assert_eq!(receipt.status.code, ResidentTerminalCode::Success as u32);
7281 assert_eq!(receipt.counts, vec![2_305]);
7282 assert_eq!(
7283 rows,
7284 (0..2_305).map(|value| vec![value]).collect::<Vec<_>>()
7285 );
7286 }
7287
7288 #[test]
7289 fn real_cuda_compact_diff_orders_high_u64_values_across_merge_tiles() {
7290 let Some(provider) = provider() else { return };
7291 let relation_schema = schema("ordered_diff", &[ScalarType::U64]);
7292 let base = 1_u64 << 40;
7293 let mut left: Vec<u64> = (0..4_093).rev().map(|value| base + value).collect();
7294 left.extend([base, base + 2_047, base + 4_092]);
7295 let mut right: Vec<u64> = (0..4_093)
7296 .rev()
7297 .filter(|value| value % 3 == 0)
7298 .map(|value| base + value)
7299 .collect();
7300 right.extend([base, base + 3, base + 4_092]);
7301 let expected: Vec<Vec<u64>> = (0..4_093)
7302 .filter(|value| value % 3 != 0)
7303 .map(|value| vec![base + value])
7304 .collect();
7305 let (receipt, rows) = compact_set_rows(
7306 &provider,
7307 relation_schema,
7308 &[left],
7309 &[right],
7310 super::OP_DIFF,
7311 expected.len() as u64,
7312 );
7313 assert_eq!(receipt.status.code, ResidentTerminalCode::Success as u32);
7314 assert_eq!(receipt.counts, vec![expected.len() as u32]);
7315 assert_eq!(rows, expected);
7316 }
7317
7318 #[test]
7319 fn real_cuda_compact_zero_arity_set_truth_table() {
7320 let Some(provider) = provider() else { return };
7321 for (left, right, union_count, diff_count) in [
7322 (false, false, 0, 0),
7323 (false, true, 1, 0),
7324 (true, false, 1, 1),
7325 (true, true, 1, 0),
7326 ] {
7327 assert_eq!(
7328 compact_nullary_set_count(&provider, left, right, super::OP_UNION),
7329 union_count
7330 );
7331 assert_eq!(
7332 compact_nullary_set_count(&provider, left, right, super::OP_DIFF),
7333 diff_count
7334 );
7335 }
7336 }
7337
7338 #[test]
7339 fn real_cuda_compact_set_max_workspace_is_canonical_and_bounded() {
7340 let Some(provider) = provider() else { return };
7341 let relation_schema = schema("max_ordered_union", &[ScalarType::U32]);
7342 let left_values: Vec<u64> = (0..65_536).rev().collect();
7343 let right_values: Vec<u64> = (0..65_536).map(|index| (index * 40_009) % 65_536).collect();
7344 let left = buffer(&provider, relation_schema.clone(), &[left_values]);
7345 let right = buffer(&provider, relation_schema.clone(), &[right_values]);
7346 let mut output = provider
7347 .prepare_resident_relation(relation_schema, 65_536)
7348 .expect("maximum compact set output")
7349 .into_buffer();
7350 let relations = vec![
7351 super::ResidentScheduleRelation::source(&left, 1).expect("maximum left"),
7352 super::ResidentScheduleRelation::source(&right, 2).expect("maximum right"),
7353 super::ResidentScheduleRelation::output(&mut output, 3),
7354 ];
7355 let operation = super::ResidentOpDescriptor {
7356 kind: super::OP_UNION,
7357 op_id: 982,
7358 out: 2,
7359 in0: 0,
7360 in1: 1,
7361 in0_generation: 1,
7362 in1_generation: 2,
7363 out_generation: 3,
7364 ..Default::default()
7365 };
7366 let wave = super::ResidentWaveDescriptor {
7367 op_count: 1,
7368 ..Default::default()
7369 };
7370 let region = super::ResidentRegionDescriptor {
7371 wave_count: 1,
7372 iteration_limit: 1,
7373 op_id: 983,
7374 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
7375 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
7376 slot_count: 3,
7377 ..Default::default()
7378 };
7379 let schedule = provider
7380 .prepare_resident_schedule(relations, &[operation], &[wave], &[region], &[], &[], &[2])
7381 .expect("prepare maximum compact set schedule");
7382 let stream = provider
7383 .device()
7384 .inner()
7385 .stream()
7386 .context()
7387 .new_stream()
7388 .expect("maximum compact set stream");
7389 let mut graph = provider
7390 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
7391 .expect("capture maximum compact set schedule");
7392
7393 for _ in 0..20 {
7394 graph.launch().expect("maximum compact set warmup launch");
7395 let receipt = graph
7396 .synchronize_and_observe()
7397 .expect("maximum compact set warmup receipt");
7398 assert_eq!(receipt.status.code, ResidentTerminalCode::Success as u32);
7399 }
7400 provider.reset_host_transfer_stats();
7401 provider.reset_d2h_transfer_count();
7402 provider.reset_untracked_metadata_dtoh_count();
7403 let mut durations = Vec::with_capacity(100);
7404 for _ in 0..100 {
7405 let started = Instant::now();
7406 graph.launch().expect("maximum compact set measured launch");
7407 let receipt = graph
7408 .synchronize_and_observe()
7409 .expect("maximum compact set measured receipt");
7410 durations.push(started.elapsed());
7411 assert_eq!(receipt.status.code, ResidentTerminalCode::Success as u32);
7412 assert_eq!(receipt.counts, vec![65_536]);
7413 }
7414 durations.sort_unstable();
7415 let median: Duration = durations[durations.len() / 2];
7416 let p95: Duration = durations[durations.len() * 95 / 100];
7417 eprintln!(
7418 "maximum compact set launch median_us={} p95_us={}",
7419 median.as_micros(),
7420 p95.as_micros()
7421 );
7422 let transfers = provider.host_transfer_stats();
7423 assert_eq!(transfers.htod_calls, 0);
7424 assert_eq!(transfers.htod_bytes, 0);
7425 assert_eq!(transfers.dtoh_calls, 0);
7426 assert_eq!(transfers.dtoh_bytes, 0);
7427 assert_eq!(provider.d2h_transfer_count(), 0);
7428 assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
7429
7430 let ordered = rows_in_device_order(
7431 &provider,
7432 graph.relation(2).expect("maximum compact set relation"),
7433 );
7434 assert_eq!(ordered.len(), 65_536);
7435 assert!(ordered
7436 .iter()
7437 .enumerate()
7438 .all(|(index, row)| row == &[index as u64]));
7439 }
7440
7441 #[test]
7442 fn real_cuda_compact_schedule_matches_primitive_chain_at_arity_seventeen() {
7443 let Some(provider) = provider() else { return };
7444 let left_types = [
7445 ScalarType::Symbol,
7446 ScalarType::U32,
7447 ScalarType::U64,
7448 ScalarType::U32,
7449 ScalarType::U64,
7450 ScalarType::U32,
7451 ScalarType::U64,
7452 ScalarType::U32,
7453 ScalarType::U64,
7454 ];
7455 let projected_types = &left_types[..8];
7456 let right_types = left_types;
7457 let left_columns = vec![
7458 vec![5, 2, 5, 4],
7459 vec![9, 7, 11, 12],
7460 vec![100, 200, 300, 400],
7461 vec![13, 23, 33, 43],
7462 vec![1_000, 2_000, 3_000, 4_000],
7463 vec![15, 25, 35, 45],
7464 vec![6_000, 7_000, 8_000, 9_000],
7465 vec![17, 27, 37, 47],
7466 vec![10_000, 11_000, 12_000, 13_000],
7467 ];
7468 let right_columns = vec![
7469 vec![5, 5, 8],
7470 vec![50, 60, 80],
7471 vec![500, 600, 800],
7472 vec![53, 63, 83],
7473 vec![5_000, 6_000, 8_000],
7474 vec![55, 65, 85],
7475 vec![6_000, 7_000, 9_000],
7476 vec![57, 67, 87],
7477 vec![7_000, 8_000, 10_000],
7478 ];
7479 let left = buffer(&provider, schema("left", &left_types), &left_columns);
7480 let right = buffer(&provider, schema("right", &right_types), &right_columns);
7481 let projected_schema = schema("projected", projected_types);
7482 let mut joined_columns = projected_schema.columns.clone();
7483 joined_columns.extend(right.schema().columns.iter().cloned());
7484 let joined_schema = Schema::new(joined_columns);
7485 assert_eq!(joined_schema.arity(), 17);
7486
7487 let left_row_zero: Vec<u64> = left_columns[..8].iter().map(|column| column[0]).collect();
7488 let left_row_two: Vec<u64> = left_columns[..8].iter().map(|column| column[2]).collect();
7489 let right_row_zero: Vec<u64> = right_columns.iter().map(|column| column[0]).collect();
7490 let right_row_one: Vec<u64> = right_columns.iter().map(|column| column[1]).collect();
7491 let mut duplicate = left_row_zero.clone();
7492 duplicate.extend(right_row_zero);
7493 let new_row: Vec<u64> = (0..17).map(|column| 90_000 + column as u64).collect();
7494 let union_extra = buffer(
7495 &provider,
7496 joined_schema.clone(),
7497 &columns_from_rows(&[duplicate, new_row]),
7498 );
7499 let mut removed = left_row_two;
7500 removed.extend(right_row_one);
7501 let diff_right = buffer(
7502 &provider,
7503 joined_schema.clone(),
7504 &columns_from_rows(&[removed]),
7505 );
7506
7507 let comparisons = [
7508 ResidentFilterComparison::new(
7509 ResidentFilterOperand::Column(0),
7510 CompareOp::Eq,
7511 ResidentFilterOperand::Constant(ResidentScalar::Symbol(5)),
7512 ),
7513 ResidentFilterComparison::new(
7514 ResidentFilterOperand::Column(1),
7515 CompareOp::Gt,
7516 ResidentFilterOperand::Constant(ResidentScalar::U32(8)),
7517 ),
7518 ResidentFilterComparison::new(
7519 ResidentFilterOperand::Column(2),
7520 CompareOp::Le,
7521 ResidentFilterOperand::Constant(ResidentScalar::U64(300)),
7522 ),
7523 ];
7524 let project_expressions: Vec<_> = (0..8).map(ResidentProjectExpr::Column).collect();
7525 let primitive_filter = provider
7526 .prepare_resident_relation(left.schema().clone(), 4)
7527 .expect("primitive filter output");
7528 let primitive_project = provider
7529 .prepare_resident_relation(projected_schema.clone(), 4)
7530 .expect("primitive project output");
7531 let primitive_join = provider
7532 .prepare_resident_relation(joined_schema.clone(), 4)
7533 .expect("primitive join output");
7534 let primitive_union = provider
7535 .prepare_resident_relation(joined_schema.clone(), 5)
7536 .expect("primitive union output");
7537 let primitive_final = provider
7538 .prepare_resident_relation(joined_schema.clone(), 5)
7539 .expect("primitive diff output");
7540 let filter_workspace = provider
7541 .prepare_resident_filter_workspace(&left, &comparisons)
7542 .expect("primitive filter workspace");
7543 let project_workspace = provider
7544 .prepare_resident_project_workspace(
7545 primitive_filter.buffer(),
7546 &projected_schema,
7547 &project_expressions,
7548 )
7549 .expect("primitive project workspace");
7550 let join_workspace = provider
7551 .prepare_resident_join_workspace(3)
7552 .expect("primitive join workspace");
7553 let union_workspace = provider
7554 .prepare_resident_set_workspace(6)
7555 .expect("primitive union workspace");
7556 let diff_workspace = provider
7557 .prepare_resident_set_workspace(6)
7558 .expect("primitive diff workspace");
7559 let control = provider
7560 .prepare_resident_convergence_control()
7561 .expect("primitive control");
7562 let primitive_stream = provider
7563 .device()
7564 .inner()
7565 .stream()
7566 .context()
7567 .new_stream()
7568 .expect("primitive stream");
7569 let primitive_graph = CapturedCudaGraph::capture_on_stream(&primitive_stream, || {
7570 provider.record_resident_control_initialize_on_stream(&control, &primitive_stream)?;
7571 provider.record_resident_filter_on_stream(
7572 &left,
7573 &primitive_filter,
7574 &filter_workspace,
7575 &control,
7576 101,
7577 &primitive_stream,
7578 )?;
7579 provider.record_resident_project_on_stream(
7580 primitive_filter.buffer(),
7581 &primitive_project,
7582 &project_workspace,
7583 &control,
7584 102,
7585 &primitive_stream,
7586 )?;
7587 provider.record_resident_join_on_stream(
7588 ResidentJoinKind::Inner,
7589 primitive_project.buffer(),
7590 0,
7591 &right,
7592 0,
7593 &primitive_join,
7594 &join_workspace,
7595 &control,
7596 103,
7597 &primitive_stream,
7598 )?;
7599 provider.record_resident_union_on_stream(
7600 primitive_join.buffer(),
7601 &union_extra,
7602 &primitive_union,
7603 &union_workspace,
7604 &control,
7605 104,
7606 &primitive_stream,
7607 )?;
7608 provider.record_resident_diff_on_stream(
7609 primitive_union.buffer(),
7610 &diff_right,
7611 &primitive_final,
7612 &diff_workspace,
7613 &control,
7614 105,
7615 &primitive_stream,
7616 )
7617 })
7618 .expect("primitive chain capture");
7619 primitive_graph
7620 .launch(&primitive_stream)
7621 .expect("primitive chain launch");
7622 primitive_stream
7623 .synchronize()
7624 .expect("primitive chain synchronization");
7625 let expected = normalized_rows(&provider, primitive_final.buffer());
7626
7627 let mut scheduled_filter = provider
7628 .prepare_resident_relation(left.schema().clone(), 4)
7629 .expect("scheduled filter output")
7630 .into_buffer();
7631 let mut scheduled_project = provider
7632 .prepare_resident_relation(projected_schema.clone(), 4)
7633 .expect("scheduled project output")
7634 .into_buffer();
7635 let mut scheduled_join = provider
7636 .prepare_resident_relation(joined_schema.clone(), 4)
7637 .expect("scheduled join output")
7638 .into_buffer();
7639 let mut scheduled_union = provider
7640 .prepare_resident_relation(joined_schema.clone(), 5)
7641 .expect("scheduled union output")
7642 .into_buffer();
7643 let mut scheduled_final = provider
7644 .prepare_resident_relation(joined_schema, 5)
7645 .expect("scheduled diff output")
7646 .into_buffer();
7647 let relations = vec![
7648 super::ResidentScheduleRelation::source(&left, 10).expect("left source"),
7649 super::ResidentScheduleRelation::source(&right, 11).expect("right source"),
7650 super::ResidentScheduleRelation::source(&union_extra, 12).expect("union source"),
7651 super::ResidentScheduleRelation::source(&diff_right, 13).expect("diff source"),
7652 super::ResidentScheduleRelation::output(&mut scheduled_filter, 14),
7653 super::ResidentScheduleRelation::output(&mut scheduled_project, 15),
7654 super::ResidentScheduleRelation::output(&mut scheduled_join, 16),
7655 super::ResidentScheduleRelation::output(&mut scheduled_union, 17),
7656 super::ResidentScheduleRelation::output(&mut scheduled_final, 18),
7657 ];
7658 let ops = [
7659 super::ResidentOpDescriptor {
7660 kind: super::OP_FILTER,
7661 flags: 0,
7662 op_id: 101,
7663 out: 4,
7664 in0: 0,
7665 in1: 0,
7666 in0_generation: 10,
7667 in1_generation: 0,
7668 out_generation: 14,
7669 aux_offset: 0,
7670 aux_count: 3,
7671 left_key: 0,
7672 right_key: 0,
7673 scan_delta: 0,
7674 filter_delta: 0,
7675 schema_winner_head: 0,
7676 schema_winner_id: 0,
7677 reserved: 0,
7678 },
7679 super::ResidentOpDescriptor {
7680 kind: super::OP_PROJECT,
7681 flags: 0,
7682 op_id: 102,
7683 out: 5,
7684 in0: 4,
7685 in1: 0,
7686 in0_generation: 14,
7687 in1_generation: 0,
7688 out_generation: 15,
7689 aux_offset: 0,
7690 aux_count: 8,
7691 left_key: 0,
7692 right_key: 0,
7693 scan_delta: 0,
7694 filter_delta: 0,
7695 schema_winner_head: 0,
7696 schema_winner_id: 0,
7697 reserved: 0,
7698 },
7699 super::ResidentOpDescriptor {
7700 kind: super::OP_JOIN_INNER,
7701 flags: 0,
7702 op_id: 103,
7703 out: 6,
7704 in0: 5,
7705 in1: 1,
7706 in0_generation: 15,
7707 in1_generation: 11,
7708 out_generation: 16,
7709 aux_offset: 0,
7710 aux_count: 0,
7711 left_key: 0,
7712 right_key: 0,
7713 scan_delta: 0,
7714 filter_delta: 0,
7715 schema_winner_head: 0,
7716 schema_winner_id: 0,
7717 reserved: 0,
7718 },
7719 super::ResidentOpDescriptor {
7720 kind: super::OP_UNION,
7721 flags: 0,
7722 op_id: 104,
7723 out: 7,
7724 in0: 6,
7725 in1: 2,
7726 in0_generation: 16,
7727 in1_generation: 12,
7728 out_generation: 17,
7729 aux_offset: 0,
7730 aux_count: 0,
7731 left_key: 0,
7732 right_key: 0,
7733 scan_delta: 0,
7734 filter_delta: 0,
7735 schema_winner_head: 0,
7736 schema_winner_id: 0,
7737 reserved: 0,
7738 },
7739 super::ResidentOpDescriptor {
7740 kind: super::OP_DIFF,
7741 flags: 0,
7742 op_id: 105,
7743 out: 8,
7744 in0: 7,
7745 in1: 3,
7746 in0_generation: 17,
7747 in1_generation: 13,
7748 out_generation: 18,
7749 aux_offset: 0,
7750 aux_count: 0,
7751 left_key: 0,
7752 right_key: 0,
7753 scan_delta: 0,
7754 filter_delta: 0,
7755 schema_winner_head: 0,
7756 schema_winner_id: 0,
7757 reserved: 0,
7758 },
7759 ];
7760 let filter_descriptors = [
7761 super::ResidentFilterComparisonDescriptor::column_constant(0, 0, 4, 5),
7762 super::ResidentFilterComparisonDescriptor::column_constant(1, 4, 4, 8),
7763 super::ResidentFilterComparisonDescriptor::column_constant(2, 3, 8, 300),
7764 ];
7765 let project_descriptors: Vec<_> = projected_types
7766 .iter()
7767 .enumerate()
7768 .map(|(column, scalar)| {
7769 super::ResidentProjectExpressionDescriptor::column(
7770 column as u32,
7771 scalar.size_bytes() as u32,
7772 )
7773 })
7774 .collect();
7775 let wave = super::ResidentWaveDescriptor {
7776 first_op: 0,
7777 op_count: 5,
7778 flags: 0,
7779 reserved: 0,
7780 };
7781 let region = super::ResidentRegionDescriptor {
7782 first_wave: 0,
7783 wave_count: 1,
7784 iteration_limit: 1,
7785 op_id: 199,
7786 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
7787 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
7788 first_slot: 0,
7789 slot_count: 9,
7790 generation_offset: 0,
7791 };
7792 let schedule = provider
7793 .prepare_resident_schedule(
7794 relations,
7795 &ops,
7796 &[wave],
7797 &[region],
7798 &filter_descriptors,
7799 &project_descriptors,
7800 &[8],
7801 )
7802 .expect("prepare compact resident schedule");
7803 let stream = provider
7804 .device()
7805 .inner()
7806 .stream()
7807 .context()
7808 .new_stream()
7809 .expect("compact schedule stream");
7810 provider.reset_host_transfer_stats();
7811 provider.reset_d2h_transfer_count();
7812 provider.reset_untracked_metadata_dtoh_count();
7813 let mut graph = provider
7814 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
7815 .expect("compact schedule capture");
7816 assert_eq!(graph.node_count().expect("node count"), 1);
7817 assert_eq!(
7818 graph.nodes().expect("nodes")[0].kind,
7819 CudaGraphNodeKind::Kernel
7820 );
7821 graph.launch().expect("compact schedule launch");
7822 let receipt = graph
7823 .synchronize_and_observe()
7824 .expect("compact schedule receipt");
7825 let transfers = provider.host_transfer_stats();
7826 let launch_metadata = provider.host_launch_metadata_transfer_stats();
7827 assert_eq!(transfers.htod_calls, 0);
7828 assert_eq!(transfers.htod_bytes, 0);
7829 assert_eq!(transfers.dtoh_calls, 0);
7830 assert_eq!(transfers.dtoh_bytes, 0);
7831 assert_eq!(launch_metadata.htod_calls, 0);
7832 assert_eq!(launch_metadata.htod_bytes, 0);
7833 assert_eq!(provider.d2h_transfer_count(), 0);
7834 assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
7835
7836 assert_eq!(receipt.status.code, ResidentTerminalCode::Success as u32);
7837 assert_eq!(receipt.status.op_id, 199);
7838 assert_eq!(receipt.counts, vec![4]);
7839 let scheduled_final = graph.relation(8).expect("scheduled final relation");
7840 assert_eq!(scheduled_final.arity(), 17);
7841 assert_eq!(scheduled_final.cached_row_count(), Some(4));
7842 assert!(!scheduled_final.canonical_full_row_set_certified());
7843 assert_eq!(rows_in_device_order(&provider, scheduled_final), expected);
7844 }
7845
7846 #[test]
7847 fn real_cuda_compact_set_overflow_is_exact_and_preserves_output_storage() {
7848 let Some(provider) = provider() else { return };
7849 let relation_schema = schema("set_overflow", &[ScalarType::U32]);
7850 let left = buffer(&provider, relation_schema.clone(), &[vec![1, 2]]);
7851 let right = buffer(&provider, relation_schema.clone(), &[vec![3, 4]]);
7852 let sentinels = [0xdead_beef_u64, 0xcafe_babe];
7853 let mut output = buffer(&provider, relation_schema, &[sentinels.to_vec()]);
7854 let relations = vec![
7855 super::ResidentScheduleRelation::source(&left, 1).expect("left source"),
7856 super::ResidentScheduleRelation::source(&right, 2).expect("right source"),
7857 super::ResidentScheduleRelation::output(&mut output, 3),
7858 ];
7859 let operation = super::ResidentOpDescriptor {
7860 kind: super::OP_UNION,
7861 op_id: 801,
7862 out: 2,
7863 in0: 0,
7864 in1: 1,
7865 in0_generation: 1,
7866 in1_generation: 2,
7867 out_generation: 3,
7868 ..Default::default()
7869 };
7870 let wave = super::ResidentWaveDescriptor {
7871 first_op: 0,
7872 op_count: 1,
7873 ..Default::default()
7874 };
7875 let region = super::ResidentRegionDescriptor {
7876 first_wave: 0,
7877 wave_count: 1,
7878 iteration_limit: 1,
7879 op_id: 802,
7880 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
7881 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
7882 first_slot: 0,
7883 slot_count: 3,
7884 generation_offset: 0,
7885 };
7886 let schedule = provider
7887 .prepare_resident_schedule(relations, &[operation], &[wave], &[region], &[], &[], &[2])
7888 .expect("prepare set overflow schedule");
7889 let stream = provider
7890 .device()
7891 .inner()
7892 .stream()
7893 .context()
7894 .new_stream()
7895 .expect("set overflow stream");
7896 let mut graph = provider
7897 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
7898 .expect("capture set overflow schedule");
7899 graph.launch().expect("launch set overflow schedule");
7900 let receipt = graph
7901 .synchronize_and_observe()
7902 .expect("set overflow receipt");
7903 assert_eq!(
7904 receipt.status.code,
7905 ResidentTerminalCode::CapacityOverflow as u32
7906 );
7907 assert_eq!(receipt.status.op_id, 801);
7908 assert_eq!(receipt.status.required, 4);
7909 assert_eq!(receipt.status.capacity, 2);
7910 assert_eq!(receipt.counts, vec![0]);
7911 let raw: Vec<u8> = provider
7912 .device()
7913 .inner()
7914 .dtoh_sync_copy(
7915 graph
7916 .relation(2)
7917 .expect("set overflow output")
7918 .column(0)
7919 .expect("set overflow column"),
7920 )
7921 .expect("set overflow storage");
7922 let expected: Vec<u8> = sentinels
7923 .iter()
7924 .flat_map(|value| (*value as u32).to_le_bytes())
7925 .collect();
7926 assert_eq!(raw, expected);
7927 }
7928
7929 #[test]
7930 fn real_cuda_compact_join_overflow_is_exact_and_preserves_output_storage() {
7931 let Some(provider) = provider() else { return };
7932 let left_schema = schema("join_left", &[ScalarType::U32]);
7933 let right_schema = schema("join_right", &[ScalarType::U32]);
7934 let mut output_columns = left_schema.columns.clone();
7935 output_columns.extend(right_schema.columns.iter().cloned());
7936 let output_schema = Schema::new(output_columns);
7937 let left = buffer(&provider, left_schema, &[vec![1, 1]]);
7938 let right = buffer(&provider, right_schema, &[vec![1, 1]]);
7939 let sentinels = [
7940 vec![0x1111_1111_u64, 0x2222_2222],
7941 vec![0x3333_3333_u64, 0x4444_4444],
7942 ];
7943 let mut output = buffer(&provider, output_schema, &sentinels);
7944 let relations = vec![
7945 super::ResidentScheduleRelation::source(&left, 1).expect("left source"),
7946 super::ResidentScheduleRelation::source(&right, 2).expect("right source"),
7947 super::ResidentScheduleRelation::output(&mut output, 3),
7948 ];
7949 let operation = super::ResidentOpDescriptor {
7950 kind: super::OP_JOIN_INNER,
7951 op_id: 811,
7952 out: 2,
7953 in0: 0,
7954 in1: 1,
7955 in0_generation: 1,
7956 in1_generation: 2,
7957 out_generation: 3,
7958 left_key: 0,
7959 right_key: 0,
7960 ..Default::default()
7961 };
7962 let wave = super::ResidentWaveDescriptor {
7963 first_op: 0,
7964 op_count: 1,
7965 ..Default::default()
7966 };
7967 let region = super::ResidentRegionDescriptor {
7968 first_wave: 0,
7969 wave_count: 1,
7970 iteration_limit: 1,
7971 op_id: 812,
7972 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
7973 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
7974 first_slot: 0,
7975 slot_count: 3,
7976 generation_offset: 0,
7977 };
7978 let schedule = provider
7979 .prepare_resident_schedule(relations, &[operation], &[wave], &[region], &[], &[], &[2])
7980 .expect("prepare join overflow schedule");
7981 let stream = provider
7982 .device()
7983 .inner()
7984 .stream()
7985 .context()
7986 .new_stream()
7987 .expect("join overflow stream");
7988 let mut graph = provider
7989 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
7990 .expect("capture join overflow schedule");
7991 graph.launch().expect("launch join overflow schedule");
7992 let receipt = graph
7993 .synchronize_and_observe()
7994 .expect("join overflow receipt");
7995 assert_eq!(
7996 receipt.status.code,
7997 ResidentTerminalCode::CapacityOverflow as u32
7998 );
7999 assert_eq!(receipt.status.op_id, 811);
8000 assert_eq!(receipt.status.required, 4);
8001 assert_eq!(receipt.status.capacity, 2);
8002 assert_eq!(receipt.counts, vec![0]);
8003 for (column, expected) in sentinels.iter().enumerate() {
8004 let raw: Vec<u8> = provider
8005 .device()
8006 .inner()
8007 .dtoh_sync_copy(
8008 graph
8009 .relation(2)
8010 .expect("join overflow output")
8011 .column(column)
8012 .expect("join overflow column"),
8013 )
8014 .expect("join overflow storage");
8015 let expected: Vec<u8> = expected
8016 .iter()
8017 .flat_map(|value| (*value as u32).to_le_bytes())
8018 .collect();
8019 assert_eq!(raw, expected, "column {column} was written on overflow");
8020 }
8021 }
8022
8023 #[test]
8024 fn real_cuda_compact_project_overflow_is_sticky_and_preserves_storage() {
8025 let Some(provider) = provider() else { return };
8026 let relation_schema = schema("project_overflow", &[ScalarType::U32]);
8027 let input = buffer(&provider, relation_schema.clone(), &[vec![1, 2, 3, 4]]);
8028 let sentinels = [0xaaaa_aaaa_u64, 0xbbbb_bbbb];
8029 let downstream_sentinels = [0xcccc_cccc_u64, 0xdddd_dddd];
8030 let mut overflow_output = buffer(&provider, relation_schema.clone(), &[sentinels.to_vec()]);
8031 let mut downstream = buffer(&provider, relation_schema, &[downstream_sentinels.to_vec()]);
8032 let relations = vec![
8033 super::ResidentScheduleRelation::source(&input, 1).expect("project source"),
8034 super::ResidentScheduleRelation::output(&mut overflow_output, 2),
8035 super::ResidentScheduleRelation::output(&mut downstream, 3),
8036 ];
8037 let operations = [
8038 super::ResidentOpDescriptor {
8039 kind: super::OP_PROJECT,
8040 op_id: 821,
8041 out: 1,
8042 in0: 0,
8043 in0_generation: 1,
8044 out_generation: 2,
8045 aux_offset: 0,
8046 aux_count: 1,
8047 ..Default::default()
8048 },
8049 super::ResidentOpDescriptor {
8050 kind: super::OP_PROJECT,
8051 op_id: 822,
8052 out: 2,
8053 in0: 1,
8054 in0_generation: 2,
8055 out_generation: 3,
8056 aux_offset: 1,
8057 aux_count: 1,
8058 ..Default::default()
8059 },
8060 ];
8061 let expression = super::ResidentProjectExpressionDescriptor::column(0, 4);
8062 let wave = super::ResidentWaveDescriptor {
8063 first_op: 0,
8064 op_count: 2,
8065 ..Default::default()
8066 };
8067 let region = super::ResidentRegionDescriptor {
8068 first_wave: 0,
8069 wave_count: 1,
8070 iteration_limit: 1,
8071 op_id: 823,
8072 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
8073 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
8074 first_slot: 0,
8075 slot_count: 3,
8076 generation_offset: 0,
8077 };
8078 let schedule = provider
8079 .prepare_resident_schedule(
8080 relations,
8081 &operations,
8082 &[wave],
8083 &[region],
8084 &[],
8085 &[expression, expression],
8086 &[1, 2],
8087 )
8088 .expect("prepare project overflow schedule");
8089 let stream = provider
8090 .device()
8091 .inner()
8092 .stream()
8093 .context()
8094 .new_stream()
8095 .expect("project overflow stream");
8096 let mut graph = provider
8097 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
8098 .expect("capture project overflow schedule");
8099 graph.launch().expect("launch project overflow schedule");
8100 let receipt = graph
8101 .synchronize_and_observe()
8102 .expect("project overflow receipt");
8103 assert_eq!(
8104 receipt.status.code,
8105 ResidentTerminalCode::CapacityOverflow as u32
8106 );
8107 assert_eq!(receipt.status.op_id, 821);
8108 assert_eq!(receipt.status.required, 4);
8109 assert_eq!(receipt.status.capacity, 2);
8110 assert_eq!(receipt.counts, vec![0, 0]);
8111 for (slot, expected) in [(1_usize, sentinels), (2, downstream_sentinels)] {
8112 let raw: Vec<u8> = provider
8113 .device()
8114 .inner()
8115 .dtoh_sync_copy(
8116 graph
8117 .relation(slot)
8118 .expect("project output")
8119 .column(0)
8120 .expect("project column"),
8121 )
8122 .expect("project storage");
8123 let expected: Vec<u8> = expected
8124 .iter()
8125 .flat_map(|value| (*value as u32).to_le_bytes())
8126 .collect();
8127 assert_eq!(raw, expected, "slot {slot} was written after overflow");
8128 }
8129 }
8130
8131 #[test]
8132 fn real_cuda_compact_filter_overflow_is_exact_and_preserves_output_storage() {
8133 let Some(provider) = provider() else { return };
8134 let types = [ScalarType::Symbol, ScalarType::U32, ScalarType::U64];
8135 let relation_schema = schema("overflow", &types);
8136 let input = buffer(
8137 &provider,
8138 relation_schema.clone(),
8139 &[
8140 vec![1, 2, 3, 4],
8141 vec![10, 20, 30, 40],
8142 vec![100, 200, 300, 400],
8143 ],
8144 );
8145 let sentinel_columns = [
8146 vec![0xdead_beef, 0xcafe_babe],
8147 vec![0x1234_5678, 0x8765_4321],
8148 vec![0x0123_4567_89ab_cdef, 0xfedc_ba98_7654_3210],
8149 ];
8150 let mut output = buffer(&provider, relation_schema, &sentinel_columns);
8151 let relations = vec![
8152 super::ResidentScheduleRelation::source(&input, 1).expect("input source"),
8153 super::ResidentScheduleRelation::output(&mut output, 2),
8154 ];
8155 let op = super::ResidentOpDescriptor {
8156 kind: super::OP_FILTER,
8157 flags: 0,
8158 op_id: 301,
8159 out: 1,
8160 in0: 0,
8161 in1: 0,
8162 in0_generation: 1,
8163 in1_generation: 0,
8164 out_generation: 2,
8165 aux_offset: 0,
8166 aux_count: 1,
8167 left_key: 0,
8168 right_key: 0,
8169 scan_delta: 0,
8170 filter_delta: 0,
8171 schema_winner_head: 0,
8172 schema_winner_id: 0,
8173 reserved: 0,
8174 };
8175 let wave = super::ResidentWaveDescriptor {
8176 first_op: 0,
8177 op_count: 1,
8178 flags: 0,
8179 reserved: 0,
8180 };
8181 let region = super::ResidentRegionDescriptor {
8182 first_wave: 0,
8183 wave_count: 1,
8184 iteration_limit: 1,
8185 op_id: 399,
8186 flags: super::RESIDENT_SCHEDULE_REGION_INITIALIZE
8187 | super::RESIDENT_SCHEDULE_REGION_FINALIZE,
8188 first_slot: 0,
8189 slot_count: 2,
8190 generation_offset: 0,
8191 };
8192 let comparisons = [super::ResidentFilterComparisonDescriptor::column_constant(
8193 0, 5, 4, 0,
8194 )];
8195 let schedule = provider
8196 .prepare_resident_schedule(
8197 relations,
8198 &[op],
8199 &[wave],
8200 &[region],
8201 &comparisons,
8202 &[],
8203 &[1],
8204 )
8205 .expect("prepare overflow schedule");
8206 let stream = provider
8207 .device()
8208 .inner()
8209 .stream()
8210 .context()
8211 .new_stream()
8212 .expect("overflow stream");
8213 provider.reset_host_transfer_stats();
8214 provider.reset_d2h_transfer_count();
8215 provider.reset_untracked_metadata_dtoh_count();
8216 let mut graph = provider
8217 .capture_resident_schedule(schedule, 0, Arc::clone(&stream))
8218 .expect("overflow capture");
8219 assert_eq!(graph.node_count().expect("overflow node count"), 1);
8220 graph.launch().expect("overflow launch");
8221 let receipt = graph.synchronize_and_observe().expect("overflow receipt");
8222 let transfers = provider.host_transfer_stats();
8223 let launch_metadata = provider.host_launch_metadata_transfer_stats();
8224 assert_eq!(transfers.htod_calls + transfers.dtoh_calls, 0);
8225 assert_eq!(transfers.htod_bytes + transfers.dtoh_bytes, 0);
8226 assert_eq!(launch_metadata.htod_calls, 0);
8227 assert_eq!(launch_metadata.htod_bytes, 0);
8228 assert_eq!(provider.d2h_transfer_count(), 0);
8229 assert_eq!(provider.untracked_metadata_dtoh_count(), 0);
8230
8231 assert_eq!(
8232 receipt.status.code,
8233 ResidentTerminalCode::CapacityOverflow as u32
8234 );
8235 assert_eq!(receipt.status.op_id, 301);
8236 assert_eq!(
8237 receipt.status.resource_code,
8238 ResidentResourceCode::OutputRows as u32
8239 );
8240 assert_eq!(receipt.status.required, 4);
8241 assert_eq!(receipt.status.capacity, 2);
8242 assert_eq!(receipt.counts, vec![0]);
8243
8244 let output = graph.relation(1).expect("overflow output relation");
8245 for (column, expected) in sentinel_columns.iter().enumerate() {
8246 let raw: Vec<u8> = provider
8247 .device()
8248 .inner()
8249 .dtoh_sync_copy(output.column(column).expect("output column"))
8250 .expect("raw output storage");
8251 let expected: Vec<u8> = if types[column].size_bytes() == 4 {
8252 expected
8253 .iter()
8254 .flat_map(|value| (*value as u32).to_le_bytes())
8255 .collect()
8256 } else {
8257 expected
8258 .iter()
8259 .flat_map(|value| value.to_le_bytes())
8260 .collect()
8261 };
8262 assert_eq!(raw, expected, "column {column} was written on overflow");
8263 }
8264 }
8265}