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