1use std::marker::PhantomData;
7
8use crate::{DeviceRepr, DeviceSlice, KernelScalar, LaunchAsync, LaunchConfig};
9use xlog_core::{Result, ScalarType, XlogError};
10
11use super::{filter_kernels, scan_kernels, RawCudaView, FILTER_MODULE, SCAN_MODULE};
12use crate::device_runtime::StreamId;
13use crate::launch::LaunchRecorder;
14use crate::memory::{CudaColumn, TrackedCudaSlice};
15use crate::type_seam::GpuScalar;
16use crate::{CompareOp, CudaBuffer};
17
18impl super::CudaKernelProvider {
19 pub fn compare_columns<T: GpuScalar>(
29 &self,
30 input: &CudaBuffer,
31 left: usize,
32 right: usize,
33 op: CompareOp,
34 ) -> Result<TrackedCudaSlice<u8>> {
35 self.compare_columns_mask::<T>(
36 input,
37 left,
38 right,
39 op,
40 T::allowed_scalar_types(),
41 T::compare_col_kernel(),
42 )
43 }
44
45 pub fn filter<T: GpuScalar>(
53 &self,
54 input: &CudaBuffer,
55 col: usize,
56 value: T,
57 op: CompareOp,
58 ) -> Result<CudaBuffer> {
59 if Self::use_recorded_filters_env() {
64 if let Some(launch_stream) = self.recorded_op_stream_or_init() {
65 return self.filter_recorded::<T>(input, col, value, op, launch_stream);
66 }
67 }
68
69 if input.is_empty() {
70 return self.create_empty_buffer(input.schema.clone());
71 }
72
73 if T::filter_scan_phase1_kernel().is_some() {
74 self.filter_fused_scan::<T>(input, col, value, op)
76 } else {
77 let mask = self.compare_const_mask::<T>(
79 input,
80 col,
81 value,
82 op,
83 T::allowed_scalar_types(),
84 T::filter_compare_kernel(),
85 )?;
86 self.filter_by_device_mask(input, &mask)
87 }
88 }
89
90 pub fn filter_recorded<T: GpuScalar>(
130 &self,
131 input: &CudaBuffer,
132 col: usize,
133 value: T,
134 op: CompareOp,
135 launch_stream: StreamId,
136 ) -> Result<CudaBuffer> {
137 if input.is_empty() {
138 return self.create_empty_buffer(input.schema.clone());
139 }
140 if T::filter_scan_phase1_kernel().is_some() {
141 return self.filter_fused_scan_recorded::<T>(input, col, value, op, launch_stream);
142 }
143 let d_mask = self.compare_const_mask_recorded::<T>(input, col, value, op, launch_stream)?;
144 self.compact_buffer_by_device_mask_counted_recorded(input, &d_mask, launch_stream)
145 }
146
147 pub fn filter_fused_scan_recorded<T: GpuScalar>(
187 &self,
188 input: &CudaBuffer,
189 col: usize,
190 value: T,
191 op: CompareOp,
192 launch_stream: StreamId,
193 ) -> Result<CudaBuffer> {
194 let runtime = self.memory.runtime().ok_or_else(|| {
195 XlogError::Kernel(
196 "filter_fused_scan_recorded requires a runtime-backed GpuMemoryManager \
197 (constructed via with_runtime)"
198 .to_string(),
199 )
200 })?;
201 let pool = runtime.stream_pool();
202 let cu_stream = pool.resolve(launch_stream).ok_or_else(|| {
203 XlogError::Kernel(format!(
204 "filter_fused_scan_recorded: launch_stream StreamId({}) does not resolve",
205 launch_stream.0
206 ))
207 })?;
208
209 if input.num_rows() > u32::MAX as u64 {
210 return Err(XlogError::Kernel(format!(
211 "filter supports at most {} rows, got {}",
212 u32::MAX,
213 input.num_rows()
214 )));
215 }
216 if col >= input.arity() {
217 return Err(XlogError::Kernel(format!(
218 "Column index {} out of bounds (arity {})",
219 col,
220 input.arity()
221 )));
222 }
223 let col_type = input
224 .schema()
225 .column_type(col)
226 .ok_or_else(|| XlogError::Kernel("Missing column type".into()))?;
227 if !T::allowed_scalar_types().contains(&col_type) {
228 return Err(XlogError::Kernel(format!(
229 "Column {} is {:?} (expected one of {:?})",
230 col,
231 col_type,
232 T::allowed_scalar_types()
233 )));
234 }
235 if input.is_empty() {
236 return self.create_empty_buffer(input.schema.clone());
237 }
238
239 let n = input.num_rows() as usize;
240 let num_rows = input.num_rows() as u32;
241 let block_size = 256u32;
242 let num_blocks = num_rows.div_ceil(block_size);
243 let row_cap = u64::from(num_rows);
244 let device = self.device.inner();
245
246 let col_data = input
247 .column(col)
248 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col)))?;
249 let col_view = Self::column_as_typed_view::<T>(col_data, n)?;
250
251 let d_mask = self.memory.alloc::<u8>(n)?;
257 let d_prefix_sum = self.memory.alloc::<u32>(n)?;
258 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
259 let mut d_out_count = self.memory.alloc::<u32>(1)?;
260
261 let mut dst_cols: Vec<TrackedCudaSlice<u8>> = Vec::with_capacity(input.columns.len());
262 for col_idx in 0..input.columns.len() {
263 let elem_size = input
264 .schema
265 .column_type(col_idx)
266 .map(|t| t.size_bytes())
267 .unwrap_or(4);
268 let output_bytes = (row_cap as usize) * elem_size;
269 dst_cols.push(self.memory.alloc::<u8>(output_bytes)?);
270 }
271
272 let mut rec = LaunchRecorder::new_strict(launch_stream);
273 rec.read(input.num_rows_device());
274 for col_idx in 0..input.columns.len() {
275 let src_col = input
276 .column(col_idx)
277 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
278 rec.read_column(src_col);
279 }
280 rec.write(&d_mask);
281 rec.write(&d_prefix_sum);
282 rec.write(&d_block_sums);
283 rec.write(&d_out_count);
284 for dst_col in &dst_cols {
285 rec.write(dst_col);
286 }
287 rec.preflight(runtime).map_err(|e| {
288 XlogError::Kernel(format!(
289 "filter_fused_scan_recorded: launch recorder preflight failed: {}",
290 e
291 ))
292 })?;
293
294 let scan_kernel_name = T::filter_scan_phase1_kernel()
296 .expect("filter_fused_scan_recorded called without scan phase1 kernel");
297 let filter_scan_fn = device
298 .get_func(FILTER_MODULE, scan_kernel_name)
299 .ok_or_else(|| XlogError::Kernel(format!("{} kernel not found", scan_kernel_name)))?;
300 unsafe {
303 filter_scan_fn.clone().launch_on_stream(
304 &cu_stream,
305 LaunchConfig {
306 grid_dim: (num_blocks, 1, 1),
307 block_dim: (block_size, 1, 1),
308 shared_mem_bytes: 0,
309 },
310 (
311 &col_view,
312 value,
313 num_rows,
314 input.num_rows_device(),
315 op as u8,
316 &d_mask,
317 &d_prefix_sum,
318 &d_block_sums,
319 ),
320 )
321 }
322 .map_err(|e| {
323 XlogError::Kernel(format!("{} (on_stream) failed: {}", scan_kernel_name, e))
324 })?;
325
326 if num_blocks > 1 {
329 self.multiblock_scan_u32_inplace_on_stream(
330 &mut d_block_sums,
331 num_blocks,
332 &cu_stream,
333 launch_stream,
334 runtime,
335 )?;
336
337 let phase3_fn = device
338 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
339 .ok_or_else(|| {
340 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
341 })?;
342 unsafe {
344 phase3_fn.clone().launch_on_stream(
345 &cu_stream,
346 LaunchConfig {
347 grid_dim: (num_blocks, 1, 1),
348 block_dim: (block_size, 1, 1),
349 shared_mem_bytes: 0,
350 },
351 (&d_prefix_sum, &d_block_sums, num_rows),
352 )
353 }
354 .map_err(|e| {
355 XlogError::Kernel(format!("multiblock_scan_phase3 (on_stream) failed: {}", e))
356 })?;
357 }
358
359 let capture_fn = device
361 .get_func(FILTER_MODULE, filter_kernels::CAPTURE_COMPACT_COUNT)
362 .ok_or_else(|| {
363 XlogError::Kernel("capture_compact_count kernel not found".to_string())
364 })?;
365 unsafe {
367 capture_fn.clone().launch_on_stream(
368 &cu_stream,
369 LaunchConfig {
370 grid_dim: (1, 1, 1),
371 block_dim: (1, 1, 1),
372 shared_mem_bytes: 0,
373 },
374 (&d_prefix_sum, &d_mask, num_rows, &mut d_out_count),
375 )
376 }
377 .map_err(|e| {
378 XlogError::Kernel(format!("capture_compact_count (on_stream) failed: {}", e))
379 })?;
380
381 cu_stream.synchronize().map_err(|e| {
387 XlogError::Kernel(format!(
388 "filter_fused_scan_recorded: launch_stream synchronize before host scalar \
389 read failed: {}",
390 e
391 ))
392 })?;
393 let output_rows = self.dtoh_scalar_untracked(&d_out_count, 0)? as u64;
394
395 if output_rows > 0 {
400 let compact_fn = device
401 .get_func(FILTER_MODULE, filter_kernels::COMPACT_BYTES_BY_MASK)
402 .ok_or_else(|| {
403 XlogError::Kernel("compact_bytes_by_mask kernel not found".to_string())
404 })?;
405 let cfg = LaunchConfig {
406 grid_dim: (num_blocks, 1, 1),
407 block_dim: (block_size, 1, 1),
408 shared_mem_bytes: 0,
409 };
410 for (col_idx, dst_col) in dst_cols.iter().enumerate() {
411 let src_col = input
412 .column(col_idx)
413 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
414 let elem_size = input
415 .schema
416 .column_type(col_idx)
417 .map(|t| t.size_bytes())
418 .unwrap_or(4) as u32;
419 unsafe {
421 compact_fn.clone().launch_on_stream(
422 &cu_stream,
423 cfg,
424 (
425 src_col,
426 &d_mask,
427 &d_prefix_sum,
428 num_rows,
429 elem_size,
430 dst_col,
431 ),
432 )
433 }
434 .map_err(|e| {
435 XlogError::Kernel(format!("compact_bytes_by_mask (on_stream) failed: {}", e))
436 })?;
437 }
438 }
439
440 rec.commit(runtime).map_err(|e| {
443 XlogError::Kernel(format!(
444 "filter_fused_scan_recorded: launch recorder commit failed: {}",
445 e
446 ))
447 })?;
448
449 let new_columns: Vec<CudaColumn> = dst_cols.into_iter().map(|s| s.into()).collect();
450 Ok(CudaBuffer::from_columns_with_host_count(
451 new_columns,
452 row_cap,
453 d_out_count,
454 input.schema.clone(),
455 output_rows as u32,
456 ))
457 }
458
459 pub fn filter_columns_recorded<T: GpuScalar>(
480 &self,
481 input: &CudaBuffer,
482 left: usize,
483 right: usize,
484 op: CompareOp,
485 launch_stream: StreamId,
486 ) -> Result<CudaBuffer> {
487 if input.is_empty() {
488 return self.create_empty_buffer(input.schema.clone());
489 }
490 let d_mask =
491 self.compare_columns_mask_recorded::<T>(input, left, right, op, launch_stream)?;
492 self.compact_buffer_by_device_mask_counted_recorded(input, &d_mask, launch_stream)
493 }
494
495 pub(crate) fn compare_const_mask<T: KernelScalar>(
503 &self,
504 input: &CudaBuffer,
505 col: usize,
506 value: T,
507 op: CompareOp,
508 allowed_types: &[ScalarType],
509 kernel: &str,
510 ) -> Result<TrackedCudaSlice<u8>> {
511 if input.num_rows() > u32::MAX as u64 {
512 return Err(XlogError::Kernel(format!(
513 "Filter supports at most {} rows, got {}",
514 u32::MAX,
515 input.num_rows()
516 )));
517 }
518 if col >= input.arity() {
519 return Err(XlogError::Kernel(format!(
520 "Column index {} out of bounds (arity {})",
521 col,
522 input.arity()
523 )));
524 }
525
526 if input.is_empty() {
527 return self.memory.alloc::<u8>(0);
528 }
529
530 let col_type = input
531 .schema()
532 .column_type(col)
533 .ok_or_else(|| XlogError::Kernel("Missing column type".into()))?;
534 if !allowed_types.contains(&col_type) {
535 return Err(XlogError::Kernel(format!(
536 "Column {} is {:?} (expected {:?})",
537 col, col_type, allowed_types
538 )));
539 }
540
541 let num_rows = input.num_rows() as u32;
542 let expected_bytes = (num_rows as usize)
543 .checked_mul(std::mem::size_of::<T>())
544 .ok_or_else(|| XlogError::Kernel("filter compare size overflow".into()))?;
545 let col_data = input
546 .column(col)
547 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col)))?;
548 if col_data.num_bytes() != expected_bytes {
549 return Err(XlogError::Kernel(format!(
550 "Column {} has {} bytes but expected {} for {} rows",
551 col,
552 col_data.num_bytes(),
553 expected_bytes,
554 input.num_rows()
555 )));
556 }
557
558 let block_size = 256u32;
559 let num_blocks = num_rows.div_ceil(block_size);
560 let config = LaunchConfig {
561 grid_dim: (num_blocks, 1, 1),
562 block_dim: (block_size, 1, 1),
563 shared_mem_bytes: 0,
564 };
565
566 let mut d_mask = self.memory.alloc::<u8>(num_rows as usize)?;
567 let func = self
568 .device
569 .inner()
570 .get_func(FILTER_MODULE, kernel)
571 .ok_or_else(|| XlogError::Kernel("filter compare kernel not found".into()))?;
572
573 unsafe {
575 func.clone()
576 .launch(config, (col_data, value, num_rows, op as u8, &mut d_mask))
577 }
578 .map_err(|e| XlogError::Kernel(format!("filter compare failed: {}", e)))?;
579
580 Ok(d_mask)
581 }
582
583 pub fn compare_const_mask_recorded<T: GpuScalar>(
622 &self,
623 input: &CudaBuffer,
624 col: usize,
625 value: T,
626 op: CompareOp,
627 launch_stream: StreamId,
628 ) -> Result<TrackedCudaSlice<u8>> {
629 let allowed_types = T::allowed_scalar_types();
630 let kernel = T::filter_compare_kernel();
631 let runtime = self.memory.runtime().ok_or_else(|| {
632 XlogError::Kernel(
633 "compare_const_mask_recorded requires a runtime-backed GpuMemoryManager \
634 (constructed via with_runtime)"
635 .to_string(),
636 )
637 })?;
638 let pool = runtime.stream_pool();
639 let cu_stream = pool.resolve(launch_stream).ok_or_else(|| {
640 XlogError::Kernel(format!(
641 "compare_const_mask_recorded: launch_stream StreamId({}) does not resolve",
642 launch_stream.0
643 ))
644 })?;
645
646 if input.num_rows() > u32::MAX as u64 {
647 return Err(XlogError::Kernel(format!(
648 "Filter supports at most {} rows, got {}",
649 u32::MAX,
650 input.num_rows()
651 )));
652 }
653 if col >= input.arity() {
654 return Err(XlogError::Kernel(format!(
655 "Column index {} out of bounds (arity {})",
656 col,
657 input.arity()
658 )));
659 }
660
661 if input.is_empty() {
662 return self.memory.alloc::<u8>(0);
663 }
664
665 let col_type = input
666 .schema()
667 .column_type(col)
668 .ok_or_else(|| XlogError::Kernel("Missing column type".into()))?;
669 if !allowed_types.contains(&col_type) {
670 return Err(XlogError::Kernel(format!(
671 "Column {} is {:?} (expected {:?})",
672 col, col_type, allowed_types
673 )));
674 }
675
676 let num_rows = input.num_rows() as u32;
677 let expected_bytes = (num_rows as usize)
678 .checked_mul(std::mem::size_of::<T>())
679 .ok_or_else(|| XlogError::Kernel("filter compare size overflow".into()))?;
680 let col_data = input
681 .column(col)
682 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col)))?;
683 if col_data.num_bytes() != expected_bytes {
684 return Err(XlogError::Kernel(format!(
685 "Column {} has {} bytes but expected {} for {} rows",
686 col,
687 col_data.num_bytes(),
688 expected_bytes,
689 input.num_rows()
690 )));
691 }
692
693 let block_size = 256u32;
694 let num_blocks = num_rows.div_ceil(block_size);
695 let config = LaunchConfig {
696 grid_dim: (num_blocks, 1, 1),
697 block_dim: (block_size, 1, 1),
698 shared_mem_bytes: 0,
699 };
700
701 let mut d_mask = self.memory.alloc::<u8>(num_rows as usize)?;
702 let func = self
703 .device
704 .inner()
705 .get_func(FILTER_MODULE, kernel)
706 .ok_or_else(|| XlogError::Kernel("filter compare kernel not found".into()))?;
707
708 let mut rec = LaunchRecorder::new_strict(launch_stream);
713 rec.read_column(col_data);
714 rec.write(&d_mask);
715 rec.preflight(runtime).map_err(|e| {
716 XlogError::Kernel(format!(
717 "compare_const_mask_recorded: launch recorder preflight failed: {}",
718 e
719 ))
720 })?;
721
722 unsafe {
729 func.clone().launch_on_stream(
730 &cu_stream,
731 config,
732 (col_data, value, num_rows, op as u8, &mut d_mask),
733 )
734 }
735 .map_err(|e| {
736 XlogError::Kernel(format!("compare_const_mask_recorded launch failed: {}", e))
737 })?;
738
739 rec.commit(runtime).map_err(|e| {
744 XlogError::Kernel(format!(
745 "compare_const_mask_recorded: launch recorder commit failed: {}",
746 e
747 ))
748 })?;
749
750 Ok(d_mask)
751 }
752
753 pub(crate) fn compare_columns_mask<T: DeviceRepr>(
755 &self,
756 input: &CudaBuffer,
757 left: usize,
758 right: usize,
759 op: CompareOp,
760 allowed_types: &[ScalarType],
761 kernel: &str,
762 ) -> Result<TrackedCudaSlice<u8>> {
763 if input.num_rows() > u32::MAX as u64 {
764 return Err(XlogError::Kernel(format!(
765 "Filter supports at most {} rows, got {}",
766 u32::MAX,
767 input.num_rows()
768 )));
769 }
770 if left >= input.arity() || right >= input.arity() {
771 return Err(XlogError::Kernel(format!(
772 "Column indices {} or {} out of bounds (arity {})",
773 left,
774 right,
775 input.arity()
776 )));
777 }
778
779 if input.is_empty() {
780 return self.memory.alloc::<u8>(0);
781 }
782
783 let left_type = input
784 .schema()
785 .column_type(left)
786 .ok_or_else(|| XlogError::Kernel("Missing left column type".into()))?;
787 let right_type = input
788 .schema()
789 .column_type(right)
790 .ok_or_else(|| XlogError::Kernel("Missing right column type".into()))?;
791
792 if left_type != right_type {
793 return Err(XlogError::Kernel(
794 "Column-column compare requires matching types".into(),
795 ));
796 }
797 if !allowed_types.contains(&left_type) {
798 return Err(XlogError::Kernel(format!(
799 "Column type {:?} not supported for compare",
800 left_type
801 )));
802 }
803
804 let num_rows = input.num_rows() as u32;
805 let expected_bytes = (num_rows as usize)
806 .checked_mul(std::mem::size_of::<T>())
807 .ok_or_else(|| XlogError::Kernel("compare columns size overflow".into()))?;
808
809 let left_col = input
810 .column(left)
811 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", left)))?;
812 let right_col = input
813 .column(right)
814 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", right)))?;
815
816 if left_col.num_bytes() != expected_bytes || right_col.num_bytes() != expected_bytes {
817 return Err(XlogError::Kernel(format!(
818 "Compare columns expect {} bytes per column for {} rows",
819 expected_bytes,
820 input.num_rows()
821 )));
822 }
823
824 let block_size = 256u32;
825 let num_blocks = num_rows.div_ceil(block_size);
826 let config = LaunchConfig {
827 grid_dim: (num_blocks, 1, 1),
828 block_dim: (block_size, 1, 1),
829 shared_mem_bytes: 0,
830 };
831
832 let mut d_mask = self.memory.alloc::<u8>(num_rows as usize)?;
833 let func = self
834 .device
835 .inner()
836 .get_func(FILTER_MODULE, kernel)
837 .ok_or_else(|| XlogError::Kernel("filter compare kernel not found".into()))?;
838
839 unsafe {
841 func.clone().launch(
842 config,
843 (left_col, right_col, num_rows, op as u8, &mut d_mask),
844 )
845 }
846 .map_err(|e| XlogError::Kernel(format!("filter compare failed: {}", e)))?;
847
848 Ok(d_mask)
849 }
850
851 pub fn compare_columns_mask_recorded<T: GpuScalar>(
882 &self,
883 input: &CudaBuffer,
884 left: usize,
885 right: usize,
886 op: CompareOp,
887 launch_stream: StreamId,
888 ) -> Result<TrackedCudaSlice<u8>> {
889 let allowed_types = T::allowed_scalar_types();
890 let kernel = T::compare_col_kernel();
891
892 let runtime = self.memory.runtime().ok_or_else(|| {
893 XlogError::Kernel(
894 "compare_columns_mask_recorded requires a runtime-backed GpuMemoryManager \
895 (constructed via with_runtime)"
896 .to_string(),
897 )
898 })?;
899 let pool = runtime.stream_pool();
900 let cu_stream = pool.resolve(launch_stream).ok_or_else(|| {
901 XlogError::Kernel(format!(
902 "compare_columns_mask_recorded: launch_stream StreamId({}) does not resolve",
903 launch_stream.0
904 ))
905 })?;
906
907 if input.num_rows() > u32::MAX as u64 {
908 return Err(XlogError::Kernel(format!(
909 "Filter supports at most {} rows, got {}",
910 u32::MAX,
911 input.num_rows()
912 )));
913 }
914 if left >= input.arity() || right >= input.arity() {
915 return Err(XlogError::Kernel(format!(
916 "Column indices {} or {} out of bounds (arity {})",
917 left,
918 right,
919 input.arity()
920 )));
921 }
922
923 if input.is_empty() {
924 return self.memory.alloc::<u8>(0);
925 }
926
927 let left_type = input
928 .schema()
929 .column_type(left)
930 .ok_or_else(|| XlogError::Kernel("Missing left column type".into()))?;
931 let right_type = input
932 .schema()
933 .column_type(right)
934 .ok_or_else(|| XlogError::Kernel("Missing right column type".into()))?;
935 if left_type != right_type {
936 return Err(XlogError::Kernel(
937 "Column-column compare requires matching types".into(),
938 ));
939 }
940 if !allowed_types.contains(&left_type) {
941 return Err(XlogError::Kernel(format!(
942 "Column type {:?} not supported for compare",
943 left_type
944 )));
945 }
946
947 let num_rows = input.num_rows() as u32;
948 let expected_bytes = (num_rows as usize)
949 .checked_mul(std::mem::size_of::<T>())
950 .ok_or_else(|| XlogError::Kernel("compare columns size overflow".into()))?;
951 let left_col = input
952 .column(left)
953 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", left)))?;
954 let right_col = input
955 .column(right)
956 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", right)))?;
957 if left_col.num_bytes() != expected_bytes || right_col.num_bytes() != expected_bytes {
958 return Err(XlogError::Kernel(format!(
959 "Compare columns expect {} bytes per column for {} rows",
960 expected_bytes,
961 input.num_rows()
962 )));
963 }
964
965 let block_size = 256u32;
966 let num_blocks = num_rows.div_ceil(block_size);
967 let config = LaunchConfig {
968 grid_dim: (num_blocks, 1, 1),
969 block_dim: (block_size, 1, 1),
970 shared_mem_bytes: 0,
971 };
972
973 let mut d_mask = self.memory.alloc::<u8>(num_rows as usize)?;
974 let func = self
975 .device
976 .inner()
977 .get_func(FILTER_MODULE, kernel)
978 .ok_or_else(|| XlogError::Kernel("filter compare kernel not found".into()))?;
979
980 let mut rec = LaunchRecorder::new_strict(launch_stream);
984 rec.read_column(left_col);
985 rec.read_column(right_col);
986 rec.write(&d_mask);
987 rec.preflight(runtime).map_err(|e| {
988 XlogError::Kernel(format!(
989 "compare_columns_mask_recorded: launch recorder preflight failed: {}",
990 e
991 ))
992 })?;
993
994 unsafe {
1001 func.clone().launch_on_stream(
1002 &cu_stream,
1003 config,
1004 (left_col, right_col, num_rows, op as u8, &mut d_mask),
1005 )
1006 }
1007 .map_err(|e| {
1008 XlogError::Kernel(format!(
1009 "compare_columns_mask_recorded launch failed: {}",
1010 e
1011 ))
1012 })?;
1013
1014 rec.commit(runtime).map_err(|e| {
1018 XlogError::Kernel(format!(
1019 "compare_columns_mask_recorded: launch recorder commit failed: {}",
1020 e
1021 ))
1022 })?;
1023
1024 Ok(d_mask)
1025 }
1026
1027 fn filter_fused_scan<T: GpuScalar>(
1034 &self,
1035 input: &CudaBuffer,
1036 col: usize,
1037 value: T,
1038 op: CompareOp,
1039 ) -> Result<CudaBuffer> {
1040 if input.num_rows() > u32::MAX as u64 {
1041 return Err(XlogError::Kernel(format!(
1042 "filter supports at most {} rows, got {}",
1043 u32::MAX,
1044 input.num_rows()
1045 )));
1046 }
1047
1048 let n = input.num_rows() as usize;
1049 let num_rows = input.num_rows() as u32;
1050 let device = self.device.inner();
1051
1052 if col >= input.arity() {
1054 return Err(XlogError::Kernel(format!(
1055 "Column index {} out of bounds (arity {})",
1056 col,
1057 input.arity()
1058 )));
1059 }
1060
1061 let col_type = input
1063 .schema()
1064 .column_type(col)
1065 .ok_or_else(|| XlogError::Kernel("Missing column type".into()))?;
1066 if !T::allowed_scalar_types().contains(&col_type) {
1067 return Err(XlogError::Kernel(format!(
1068 "Column {} is {:?} (expected one of {:?})",
1069 col,
1070 col_type,
1071 T::allowed_scalar_types()
1072 )));
1073 }
1074
1075 let col_data = input
1077 .column(col)
1078 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col)))?;
1079 let col_view = Self::column_as_typed_view::<T>(col_data, n)?;
1080
1081 let block_size = 256u32;
1082 let num_blocks = num_rows.div_ceil(block_size);
1083 let config = LaunchConfig {
1084 grid_dim: (num_blocks, 1, 1),
1085 block_dim: (block_size, 1, 1),
1086 shared_mem_bytes: 0,
1087 };
1088
1089 let d_mask = self.memory.alloc::<u8>(n)?;
1091 let d_prefix_sum = self.memory.alloc::<u32>(n)?;
1092 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
1093
1094 let scan_kernel_name = T::filter_scan_phase1_kernel()
1095 .expect("filter_fused_scan called without scan phase1 kernel");
1096 let filter_scan_fn = device
1097 .get_func(FILTER_MODULE, scan_kernel_name)
1098 .ok_or_else(|| XlogError::Kernel(format!("{} kernel not found", scan_kernel_name)))?;
1099
1100 unsafe {
1102 filter_scan_fn.clone().launch(
1103 config,
1104 (
1105 &col_view,
1106 value,
1107 num_rows,
1108 input.num_rows_device(),
1109 op as u8,
1110 &d_mask,
1111 &d_prefix_sum,
1112 &d_block_sums,
1113 ),
1114 )
1115 }
1116 .map_err(|e| XlogError::Kernel(format!("{} failed: {}", scan_kernel_name, e)))?;
1117
1118 if num_blocks > 1 {
1119 self.multiblock_scan_u32_inplace(&mut d_block_sums, num_blocks)?;
1120
1121 let phase3_fn = device
1122 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
1123 .ok_or_else(|| {
1124 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
1125 })?;
1126
1127 unsafe {
1129 phase3_fn.clone().launch(
1130 LaunchConfig {
1131 grid_dim: (num_blocks, 1, 1),
1132 block_dim: (block_size, 1, 1),
1133 shared_mem_bytes: 0,
1134 },
1135 (&d_prefix_sum, &d_block_sums, num_rows),
1136 )
1137 }
1138 .map_err(|e| XlogError::Kernel(format!("multiblock_scan_phase3 failed: {}", e)))?;
1139 }
1140
1141 self.device.synchronize()?;
1142
1143 let d_out_count = self.capture_compact_count(&d_prefix_sum, &d_mask, num_rows)?;
1144 self.compact_buffer_by_device_mask_device_count(input, &d_mask, &d_prefix_sum, d_out_count)
1145 }
1146
1147 fn column_as_typed_view<'a, T: GpuScalar>(
1155 col: &'a CudaColumn,
1156 num_elements: usize,
1157 ) -> Result<RawCudaView<'a, T>> {
1158 let required_bytes = num_elements * T::BYTE_WIDTH;
1159 if col.num_bytes() < required_bytes {
1160 return Err(XlogError::Kernel(format!(
1161 "Column has {} bytes but {} required for {} elements of size {}",
1162 col.num_bytes(),
1163 required_bytes,
1164 num_elements,
1165 T::BYTE_WIDTH,
1166 )));
1167 }
1168 let ptr = *col.device_ptr();
1169 if T::BYTE_WIDTH > 1 && !(ptr as usize).is_multiple_of(T::BYTE_WIDTH) {
1170 return Err(XlogError::Kernel(format!(
1171 "Column device pointer is not {}-byte aligned",
1172 T::BYTE_WIDTH,
1173 )));
1174 }
1175 Ok(RawCudaView {
1176 ptr,
1177 len: num_elements,
1178 stream: col.stream().clone(),
1179 _marker: PhantomData,
1180 })
1181 }
1182
1183 pub fn prefix_sum_mask(&self, mask: &[u8]) -> Result<(Vec<u32>, u32)> {
1189 if mask.is_empty() {
1190 return Ok((vec![], 0));
1191 }
1192
1193 let n = mask.len();
1194 if n > u32::MAX as usize {
1195 return Err(XlogError::Kernel(format!(
1196 "Mask length {} exceeds u32::MAX",
1197 n
1198 )));
1199 }
1200
1201 if n <= 256 {
1203 return self.prefix_sum_mask_cpu(mask);
1204 }
1205
1206 self.prefix_sum_mask_gpu_multiblock(mask)
1208 }
1209
1210 fn prefix_sum_mask_cpu(&self, mask: &[u8]) -> Result<(Vec<u32>, u32)> {
1212 let mut prefix_sum = Vec::with_capacity(mask.len());
1213 let mut sum = 0u32;
1214 for &m in mask {
1215 prefix_sum.push(sum);
1216 sum += m as u32;
1217 }
1218 Ok((prefix_sum, sum))
1219 }
1220
1221 fn prefix_sum_mask_gpu_multiblock(&self, mask: &[u8]) -> Result<(Vec<u32>, u32)> {
1227 let n = mask.len();
1228 let device = self.device.inner();
1229 let block_size = 256u32;
1230 let num_blocks = (n as u32).div_ceil(block_size);
1231
1232 let d_mask = self
1234 .htod_sync_copy_tracked(mask)
1235 .map_err(|e| XlogError::Kernel(format!("Failed to upload mask: {}", e)))?;
1236
1237 let d_prefix_sum = self.memory.alloc::<u32>(n)?;
1239
1240 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
1242
1243 let phase1_fn = device
1245 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE1)
1246 .ok_or_else(|| {
1247 XlogError::Kernel("Failed to get multiblock_scan_phase1 kernel".to_string())
1248 })?;
1249
1250 unsafe {
1254 phase1_fn.clone().launch(
1255 LaunchConfig {
1256 grid_dim: (num_blocks, 1, 1),
1257 block_dim: (block_size, 1, 1),
1258 shared_mem_bytes: 0,
1259 },
1260 (&d_mask, &d_prefix_sum, &d_block_sums, n as u32),
1261 )
1262 }
1263 .map_err(|e| {
1264 XlogError::Kernel(format!("Failed to launch multiblock_scan_phase1: {}", e))
1265 })?;
1266
1267 if num_blocks > 1 {
1269 self.multiblock_scan_u32_inplace(&mut d_block_sums, num_blocks)?;
1270
1271 let phase3_fn = device
1273 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
1274 .ok_or_else(|| {
1275 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
1276 })?;
1277
1278 unsafe {
1282 phase3_fn.clone().launch(
1283 LaunchConfig {
1284 grid_dim: (num_blocks, 1, 1),
1285 block_dim: (block_size, 1, 1),
1286 shared_mem_bytes: 0,
1287 },
1288 (&d_prefix_sum, &d_block_sums, n as u32),
1289 )
1290 }
1291 .map_err(|e| {
1292 XlogError::Kernel(format!("Failed to launch multiblock_scan_phase3: {}", e))
1293 })?;
1294 }
1295
1296 self.device.synchronize()?;
1298
1299 let prefix_sum = device
1300 .dtoh_sync_copy(&d_prefix_sum)
1301 .map_err(|e| XlogError::Kernel(format!("Failed to download prefix_sum: {}", e)))?;
1302
1303 let count = prefix_sum[n - 1] + mask[n - 1] as u32;
1305
1306 Ok((prefix_sum, count))
1307 }
1308
1309 pub fn filter_by_mask(&self, input: &CudaBuffer, mask: &[u8]) -> Result<CudaBuffer> {
1318 if input.num_rows() == 0 {
1319 return self.create_empty_buffer(input.schema.clone());
1320 }
1321
1322 let n = input.num_rows() as usize;
1323 if mask.len() != n {
1324 return Err(XlogError::Kernel(format!(
1325 "Mask length {} doesn't match buffer rows {}",
1326 mask.len(),
1327 n
1328 )));
1329 }
1330
1331 let (prefix_sum, count) = self.prefix_sum_mask(mask)?;
1333
1334 if count == 0 {
1335 return self.create_empty_buffer(input.schema.clone());
1336 }
1337
1338 self.compact_buffer_by_mask(input, mask, &prefix_sum, count as u64)
1340 }
1341
1342 fn compact_buffer_by_mask(
1344 &self,
1345 input: &CudaBuffer,
1346 mask: &[u8],
1347 prefix_sum: &[u32],
1348 output_count: u64,
1349 ) -> Result<CudaBuffer> {
1350 let d_mask = self
1352 .htod_sync_copy_tracked(mask)
1353 .map_err(|e| XlogError::Kernel(format!("Failed to upload mask: {}", e)))?;
1354 let d_prefix_sum = self
1355 .htod_sync_copy_tracked(prefix_sum)
1356 .map_err(|e| XlogError::Kernel(format!("Failed to upload prefix_sum: {}", e)))?;
1357
1358 self.compact_buffer_by_device_mask(input, &d_mask, &d_prefix_sum, output_count)
1359 }
1360
1361 pub fn compact_buffer_by_device_mask_counted_recorded(
1414 &self,
1415 input: &CudaBuffer,
1416 d_mask: &TrackedCudaSlice<u8>,
1417 launch_stream: StreamId,
1418 ) -> Result<CudaBuffer> {
1419 let runtime = self.memory.runtime().ok_or_else(|| {
1420 XlogError::Kernel(
1421 "compact_buffer_by_device_mask_counted_recorded requires a \
1422 runtime-backed GpuMemoryManager (constructed via with_runtime)"
1423 .to_string(),
1424 )
1425 })?;
1426 let pool = runtime.stream_pool();
1427 let cu_stream = pool.resolve(launch_stream).ok_or_else(|| {
1428 XlogError::Kernel(format!(
1429 "compact_buffer_by_device_mask_counted_recorded: launch_stream \
1430 StreamId({}) does not resolve",
1431 launch_stream.0
1432 ))
1433 })?;
1434
1435 let n = input.num_rows() as u32;
1436 if n == 0 {
1437 return self.create_empty_buffer(input.schema.clone());
1438 }
1439 if d_mask.is_empty() {
1448 return Err(XlogError::Kernel(
1449 "compact_buffer_by_device_mask_counted_recorded: empty d_mask".to_string(),
1450 ));
1451 }
1452
1453 let device = self.device.inner();
1454 let block_size = 256u32;
1455 let num_blocks = n.div_ceil(block_size);
1456 let row_cap = u64::from(n);
1457
1458 let mut d_mask_clamped = self.memory.alloc::<u8>(n as usize)?;
1469 let d_prefix_sum = self.memory.alloc::<u32>(n as usize)?;
1470 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
1471 let mut d_out_count = self.memory.alloc::<u32>(1)?;
1472
1473 let mut dst_cols: Vec<TrackedCudaSlice<u8>> = Vec::with_capacity(input.columns.len());
1474 for col_idx in 0..input.columns.len() {
1475 let elem_size = input
1476 .schema
1477 .column_type(col_idx)
1478 .map(|t| t.size_bytes())
1479 .unwrap_or(4);
1480 let output_bytes = (row_cap as usize) * elem_size;
1481 dst_cols.push(self.memory.alloc::<u8>(output_bytes)?);
1482 }
1483
1484 let mut rec = LaunchRecorder::new_strict(launch_stream);
1486 rec.read(d_mask);
1487 rec.read(input.num_rows_device());
1488 for col_idx in 0..input.columns.len() {
1489 let src_col = input
1490 .column(col_idx)
1491 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
1492 rec.read_column(src_col);
1493 }
1494 rec.write(&d_mask_clamped);
1495 rec.write(&d_prefix_sum);
1496 rec.write(&d_block_sums);
1497 rec.write(&d_out_count);
1498 for dst_col in &dst_cols {
1499 rec.write(dst_col);
1500 }
1501 rec.preflight(runtime).map_err(|e| {
1502 XlogError::Kernel(format!(
1503 "compact_buffer_by_device_mask_counted_recorded: launch recorder \
1504 preflight failed: {}",
1505 e
1506 ))
1507 })?;
1508
1509 let clamp_fn = device
1511 .get_func(FILTER_MODULE, filter_kernels::MASK_CLAMP_ROWS)
1512 .ok_or_else(|| XlogError::Kernel("mask_clamp_rows kernel not found".to_string()))?;
1513 unsafe {
1515 clamp_fn.clone().launch_on_stream(
1516 &cu_stream,
1517 LaunchConfig {
1518 grid_dim: (num_blocks, 1, 1),
1519 block_dim: (block_size, 1, 1),
1520 shared_mem_bytes: 0,
1521 },
1522 (d_mask, input.num_rows_device(), n, &mut d_mask_clamped),
1523 )
1524 }
1525 .map_err(|e| XlogError::Kernel(format!("mask_clamp_rows (on_stream) failed: {}", e)))?;
1526
1527 let phase1_fn = device
1529 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE1)
1530 .ok_or_else(|| {
1531 XlogError::Kernel("Failed to get multiblock_scan_phase1 kernel".to_string())
1532 })?;
1533 unsafe {
1535 phase1_fn.clone().launch_on_stream(
1536 &cu_stream,
1537 LaunchConfig {
1538 grid_dim: (num_blocks, 1, 1),
1539 block_dim: (block_size, 1, 1),
1540 shared_mem_bytes: 0,
1541 },
1542 (&d_mask_clamped, &d_prefix_sum, &d_block_sums, n),
1543 )
1544 }
1545 .map_err(|e| {
1546 XlogError::Kernel(format!("multiblock_scan_phase1 (on_stream) failed: {}", e))
1547 })?;
1548
1549 if num_blocks > 1 {
1552 self.multiblock_scan_u32_inplace_on_stream(
1553 &mut d_block_sums,
1554 num_blocks,
1555 &cu_stream,
1556 launch_stream,
1557 runtime,
1558 )?;
1559
1560 let phase3_fn = device
1561 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
1562 .ok_or_else(|| {
1563 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
1564 })?;
1565 unsafe {
1567 phase3_fn.clone().launch_on_stream(
1568 &cu_stream,
1569 LaunchConfig {
1570 grid_dim: (num_blocks, 1, 1),
1571 block_dim: (block_size, 1, 1),
1572 shared_mem_bytes: 0,
1573 },
1574 (&d_prefix_sum, &d_block_sums, n),
1575 )
1576 }
1577 .map_err(|e| {
1578 XlogError::Kernel(format!("multiblock_scan_phase3 (on_stream) failed: {}", e))
1579 })?;
1580 }
1581
1582 let capture_fn = device
1585 .get_func(FILTER_MODULE, filter_kernels::CAPTURE_COMPACT_COUNT)
1586 .ok_or_else(|| {
1587 XlogError::Kernel("capture_compact_count kernel not found".to_string())
1588 })?;
1589 unsafe {
1599 capture_fn.clone().launch_on_stream(
1600 &cu_stream,
1601 LaunchConfig {
1602 grid_dim: (1, 1, 1),
1603 block_dim: (1, 1, 1),
1604 shared_mem_bytes: 0,
1605 },
1606 (&d_prefix_sum, &d_mask_clamped, n, &mut d_out_count),
1607 )
1608 }
1609 .map_err(|e| {
1610 XlogError::Kernel(format!("capture_compact_count (on_stream) failed: {}", e))
1611 })?;
1612
1613 cu_stream.synchronize().map_err(|e| {
1620 XlogError::Kernel(format!(
1621 "compact_buffer_by_device_mask_counted_recorded: launch_stream \
1622 synchronize before host scalar read failed: {}",
1623 e
1624 ))
1625 })?;
1626
1627 let output_rows = self.dtoh_scalar_untracked(&d_out_count, 0)? as u64;
1628
1629 if output_rows > 0 {
1634 let compact_fn = device
1635 .get_func(FILTER_MODULE, filter_kernels::COMPACT_BYTES_BY_MASK)
1636 .ok_or_else(|| {
1637 XlogError::Kernel("compact_bytes_by_mask kernel not found".to_string())
1638 })?;
1639 let grid_size = n.div_ceil(block_size);
1640 let cfg = LaunchConfig {
1641 grid_dim: (grid_size, 1, 1),
1642 block_dim: (block_size, 1, 1),
1643 shared_mem_bytes: 0,
1644 };
1645 for (col_idx, dst_col) in dst_cols.iter().enumerate() {
1646 let src_col = input
1647 .column(col_idx)
1648 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
1649 let elem_size = input
1650 .schema
1651 .column_type(col_idx)
1652 .map(|t| t.size_bytes())
1653 .unwrap_or(4) as u32;
1654 unsafe {
1661 compact_fn.clone().launch_on_stream(
1662 &cu_stream,
1663 cfg,
1664 (
1665 src_col,
1666 &d_mask_clamped,
1667 &d_prefix_sum,
1668 n,
1669 elem_size,
1670 dst_col,
1671 ),
1672 )
1673 }
1674 .map_err(|e| {
1675 XlogError::Kernel(format!("compact_bytes_by_mask (on_stream) failed: {}", e))
1676 })?;
1677 }
1678 }
1679
1680 rec.commit(runtime).map_err(|e| {
1686 XlogError::Kernel(format!(
1687 "compact_buffer_by_device_mask_counted_recorded: launch recorder \
1688 commit failed: {}",
1689 e
1690 ))
1691 })?;
1692
1693 let new_columns: Vec<CudaColumn> = dst_cols.into_iter().map(|s| s.into()).collect();
1694 Ok(CudaBuffer::from_columns_with_host_count(
1695 new_columns,
1696 row_cap,
1697 d_out_count,
1698 input.schema.clone(),
1699 output_rows as u32,
1700 ))
1701 }
1702
1703 pub fn compact_buffer_by_device_mask_counted(
1704 &self,
1705 input: &CudaBuffer,
1706 d_mask: &TrackedCudaSlice<u8>,
1707 ) -> Result<CudaBuffer> {
1708 let n = input.num_rows() as u32;
1709 if n == 0 {
1710 return self.create_empty_buffer(input.schema.clone());
1711 }
1712 if n as usize > d_mask.len() {
1713 return Err(XlogError::Kernel(format!(
1714 "compact_buffer_by_device_mask_counted: mask len {} < rows {}",
1715 d_mask.len(),
1716 n
1717 )));
1718 }
1719
1720 let device = self.device.inner();
1721 let block_size = 256u32;
1722 let num_blocks = n.div_ceil(block_size);
1723
1724 let mut d_mask_clamped = self.memory.alloc::<u8>(n as usize)?;
1725 let clamp_fn = device
1726 .get_func(FILTER_MODULE, filter_kernels::MASK_CLAMP_ROWS)
1727 .ok_or_else(|| XlogError::Kernel("mask_clamp_rows kernel not found".to_string()))?;
1728
1729 unsafe {
1731 clamp_fn.clone().launch(
1732 LaunchConfig {
1733 grid_dim: (num_blocks, 1, 1),
1734 block_dim: (block_size, 1, 1),
1735 shared_mem_bytes: 0,
1736 },
1737 (d_mask, input.num_rows_device(), n, &mut d_mask_clamped),
1738 )
1739 }
1740 .map_err(|e| XlogError::Kernel(format!("mask_clamp_rows failed: {}", e)))?;
1741
1742 let d_prefix_sum = self.memory.alloc::<u32>(n as usize)?;
1743 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
1744
1745 let phase1_fn = device
1746 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE1)
1747 .ok_or_else(|| {
1748 XlogError::Kernel("Failed to get multiblock_scan_phase1 kernel".to_string())
1749 })?;
1750
1751 unsafe {
1753 phase1_fn.clone().launch(
1754 LaunchConfig {
1755 grid_dim: (num_blocks, 1, 1),
1756 block_dim: (block_size, 1, 1),
1757 shared_mem_bytes: 0,
1758 },
1759 (&d_mask_clamped, &d_prefix_sum, &d_block_sums, n),
1760 )
1761 }
1762 .map_err(|e| XlogError::Kernel(format!("multiblock_scan_phase1 failed: {}", e)))?;
1763
1764 if num_blocks > 1 {
1765 self.multiblock_scan_u32_inplace(&mut d_block_sums, num_blocks)?;
1766
1767 let phase3_fn = device
1768 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
1769 .ok_or_else(|| {
1770 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
1771 })?;
1772
1773 unsafe {
1775 phase3_fn.clone().launch(
1776 LaunchConfig {
1777 grid_dim: (num_blocks, 1, 1),
1778 block_dim: (block_size, 1, 1),
1779 shared_mem_bytes: 0,
1780 },
1781 (&d_prefix_sum, &d_block_sums, n),
1782 )
1783 }
1784 .map_err(|e| XlogError::Kernel(format!("multiblock_scan_phase3 failed: {}", e)))?;
1785 }
1786
1787 let d_out_count = self.capture_compact_count(&d_prefix_sum, d_mask, n)?;
1788
1789 self.compact_buffer_by_device_mask_device_count(input, d_mask, &d_prefix_sum, d_out_count)
1790 }
1791
1792 pub(crate) fn capture_compact_count(
1793 &self,
1794 d_prefix_sum: &cudarc::driver::CudaSlice<u32>,
1795 d_mask: &cudarc::driver::CudaSlice<u8>,
1796 n: u32,
1797 ) -> Result<TrackedCudaSlice<u32>> {
1798 let mut d_out_count = self.memory.alloc::<u32>(1)?;
1799 let device = self.device.inner();
1800 let capture_fn = device
1801 .get_func(FILTER_MODULE, filter_kernels::CAPTURE_COMPACT_COUNT)
1802 .ok_or_else(|| {
1803 XlogError::Kernel("capture_compact_count kernel not found".to_string())
1804 })?;
1805 unsafe {
1807 capture_fn.clone().launch(
1808 LaunchConfig {
1809 grid_dim: (1, 1, 1),
1810 block_dim: (1, 1, 1),
1811 shared_mem_bytes: 0,
1812 },
1813 (d_prefix_sum, d_mask, n, &mut d_out_count),
1814 )
1815 }
1816 .map_err(|e| XlogError::Kernel(format!("capture_compact_count failed: {}", e)))?;
1817 Ok(d_out_count)
1818 }
1819
1820 pub(crate) fn compact_buffer_by_device_mask_device_count(
1821 &self,
1822 input: &CudaBuffer,
1823 d_mask: &cudarc::driver::CudaSlice<u8>,
1824 d_prefix_sum: &cudarc::driver::CudaSlice<u32>,
1825 d_out_count: TrackedCudaSlice<u32>,
1826 ) -> Result<CudaBuffer> {
1827 let mask_len = u32::try_from(d_mask.len()).map_err(|_| {
1828 XlogError::Kernel(format!(
1829 "compact_buffer_by_device_mask_device_count: mask len {} exceeds u32::MAX",
1830 d_mask.len()
1831 ))
1832 })?;
1833 let prefix_len = u32::try_from(d_prefix_sum.len()).map_err(|_| {
1834 XlogError::Kernel(format!(
1835 "compact_buffer_by_device_mask_device_count: prefix sum len {} exceeds u32::MAX",
1836 d_prefix_sum.len()
1837 ))
1838 })?;
1839 if prefix_len < mask_len {
1840 return Err(XlogError::Kernel(format!(
1841 "compact_buffer_by_device_mask_device_count: prefix sum len {} < mask len {}",
1842 prefix_len, mask_len
1843 )));
1844 }
1845 if mask_len as u64 > input.num_rows() {
1846 return Err(XlogError::Kernel(format!(
1847 "compact_buffer_by_device_mask_device_count: mask len {} > row cap {}",
1848 mask_len,
1849 input.num_rows()
1850 )));
1851 }
1852 if mask_len == 0 {
1853 return self.create_empty_buffer(input.schema.clone());
1854 }
1855 let n = mask_len;
1856 let device = self.device.inner();
1857
1858 let compact_fn = device
1859 .get_func(FILTER_MODULE, filter_kernels::COMPACT_BYTES_BY_MASK)
1860 .ok_or_else(|| {
1861 XlogError::Kernel("compact_bytes_by_mask kernel not found".to_string())
1862 })?;
1863
1864 let block_size = 256u32;
1865 let grid_size = n.div_ceil(block_size);
1866 let config = LaunchConfig {
1867 grid_dim: (grid_size, 1, 1),
1868 block_dim: (block_size, 1, 1),
1869 shared_mem_bytes: 0,
1870 };
1871
1872 let output_rows = self.dtoh_scalar_untracked(&d_out_count, 0)? as u64;
1877 let row_cap = u64::from(n);
1878
1879 if output_rows == 0 {
1880 let mut new_columns = Vec::with_capacity(input.columns.len());
1881 for col_idx in 0..input.columns.len() {
1882 let elem_size = input
1883 .schema
1884 .column_type(col_idx)
1885 .map(|t| t.size_bytes())
1886 .unwrap_or(4);
1887 let output_bytes = (row_cap as usize) * elem_size;
1888 new_columns.push(self.memory.alloc::<u8>(output_bytes)?.into());
1889 }
1890 let d_zero_rows = self.upload_device_row_count(0)?;
1891 return Ok(CudaBuffer::from_columns_with_host_count(
1892 new_columns,
1893 row_cap,
1894 d_zero_rows,
1895 input.schema.clone(),
1896 0,
1897 ));
1898 }
1899
1900 let mut new_columns = Vec::with_capacity(input.columns.len());
1901 for col_idx in 0..input.columns.len() {
1902 let src_col = input
1903 .column(col_idx)
1904 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
1905
1906 let elem_size = input
1907 .schema
1908 .column_type(col_idx)
1909 .map(|t| t.size_bytes())
1910 .unwrap_or(4) as u32;
1911
1912 let output_bytes = (row_cap as usize) * (elem_size as usize);
1913 let dst_col = self.memory.alloc::<u8>(output_bytes)?;
1914
1915 unsafe {
1919 compact_fn.clone().launch(
1920 config,
1921 (src_col, d_mask, d_prefix_sum, n, elem_size, &dst_col),
1922 )
1923 }
1924 .map_err(|e| XlogError::Kernel(format!("compact_bytes_by_mask failed: {}", e)))?;
1925
1926 new_columns.push(dst_col.into());
1927 }
1928
1929 self.device.synchronize()?;
1930
1931 Ok(CudaBuffer::from_columns_with_host_count(
1932 new_columns,
1933 row_cap,
1934 d_out_count,
1935 input.schema.clone(),
1936 output_rows as u32,
1937 ))
1938 }
1939
1940 fn compact_buffer_by_device_mask(
1941 &self,
1942 input: &CudaBuffer,
1943 d_mask: &cudarc::driver::CudaSlice<u8>,
1944 d_prefix_sum: &cudarc::driver::CudaSlice<u32>,
1945 output_count: u64,
1946 ) -> Result<CudaBuffer> {
1947 let n = input.num_rows() as u32;
1948 let device = self.device.inner();
1949
1950 let compact_fn = device
1952 .get_func(FILTER_MODULE, filter_kernels::COMPACT_BYTES_BY_MASK)
1953 .ok_or_else(|| {
1954 XlogError::Kernel("compact_bytes_by_mask kernel not found".to_string())
1955 })?;
1956
1957 let block_size = 256u32;
1958 let grid_size = n.div_ceil(block_size);
1959 let config = LaunchConfig {
1960 grid_dim: (grid_size, 1, 1),
1961 block_dim: (block_size, 1, 1),
1962 shared_mem_bytes: 0,
1963 };
1964
1965 let mut new_columns = Vec::with_capacity(input.columns.len());
1967 for col_idx in 0..input.columns.len() {
1968 let src_col = input
1969 .column(col_idx)
1970 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
1971
1972 let elem_size = input
1973 .schema
1974 .column_type(col_idx)
1975 .map(|t| t.size_bytes())
1976 .unwrap_or(4) as u32;
1977
1978 let output_bytes = (output_count as usize) * (elem_size as usize);
1979 let dst_col = self.memory.alloc::<u8>(output_bytes)?;
1980
1981 unsafe {
1985 compact_fn.clone().launch(
1986 config,
1987 (src_col, d_mask, d_prefix_sum, n, elem_size, &dst_col),
1988 )
1989 }
1990 .map_err(|e| XlogError::Kernel(format!("compact_bytes_by_mask failed: {}", e)))?;
1991
1992 new_columns.push(dst_col.into());
1993 }
1994
1995 self.device.synchronize()?;
1996
1997 self.buffer_from_columns(new_columns, output_count, input.schema.clone())
1998 }
1999
2000 pub fn filter_by_device_mask(
2001 &self,
2002 input: &CudaBuffer,
2003 d_mask: &cudarc::driver::CudaSlice<u8>,
2004 ) -> Result<CudaBuffer> {
2005 if input.is_empty() {
2006 return self.create_empty_buffer(input.schema().clone());
2007 }
2008
2009 if input.num_rows() > u32::MAX as u64 {
2010 return Err(XlogError::Kernel(format!(
2011 "Device-mask filtering supports at most {} rows, got {}",
2012 u32::MAX,
2013 input.num_rows()
2014 )));
2015 }
2016
2017 let n = input.num_rows() as u32;
2018 let device = self.device.inner();
2019
2020 let block_size = 256u32;
2021 let num_blocks = n.div_ceil(block_size);
2022
2023 let mut d_mask_clamped = self.memory.alloc::<u8>(n as usize)?;
2024 let clamp_fn = device
2025 .get_func(FILTER_MODULE, filter_kernels::MASK_CLAMP_ROWS)
2026 .ok_or_else(|| XlogError::Kernel("mask_clamp_rows kernel not found".to_string()))?;
2027
2028 unsafe {
2030 clamp_fn.clone().launch(
2031 LaunchConfig {
2032 grid_dim: (num_blocks, 1, 1),
2033 block_dim: (block_size, 1, 1),
2034 shared_mem_bytes: 0,
2035 },
2036 (d_mask, input.num_rows_device(), n, &mut d_mask_clamped),
2037 )
2038 }
2039 .map_err(|e| XlogError::Kernel(format!("mask_clamp_rows failed: {}", e)))?;
2040
2041 let d_prefix_sum = self.memory.alloc::<u32>(n as usize)?;
2042 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
2043
2044 let phase1_fn = device
2045 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE1)
2046 .ok_or_else(|| {
2047 XlogError::Kernel("Failed to get multiblock_scan_phase1 kernel".to_string())
2048 })?;
2049
2050 unsafe {
2052 phase1_fn.clone().launch(
2053 LaunchConfig {
2054 grid_dim: (num_blocks, 1, 1),
2055 block_dim: (block_size, 1, 1),
2056 shared_mem_bytes: 0,
2057 },
2058 (&d_mask_clamped, &d_prefix_sum, &d_block_sums, n),
2059 )
2060 }
2061 .map_err(|e| XlogError::Kernel(format!("multiblock_scan_phase1 failed: {}", e)))?;
2062
2063 if num_blocks > 1 {
2064 self.multiblock_scan_u32_inplace(&mut d_block_sums, num_blocks)?;
2065
2066 let phase3_fn = device
2067 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
2068 .ok_or_else(|| {
2069 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
2070 })?;
2071
2072 unsafe {
2074 phase3_fn.clone().launch(
2075 LaunchConfig {
2076 grid_dim: (num_blocks, 1, 1),
2077 block_dim: (block_size, 1, 1),
2078 shared_mem_bytes: 0,
2079 },
2080 (&d_prefix_sum, &d_block_sums, n),
2081 )
2082 }
2083 .map_err(|e| XlogError::Kernel(format!("multiblock_scan_phase3 failed: {}", e)))?;
2084 }
2085
2086 self.device.synchronize()?;
2087
2088 let d_out_count = self.capture_compact_count(&d_prefix_sum, &d_mask_clamped, n)?;
2089 self.compact_buffer_by_device_mask_device_count(
2090 input,
2091 &d_mask_clamped,
2092 &d_prefix_sum,
2093 d_out_count,
2094 )
2095 }
2096}