1use std::collections::BTreeMap;
2use std::ffi::c_void;
3
4use cudarc::driver::sys;
5use xlog_core::{AggOp, Result, ScalarType, Schema, XlogError};
6
7use super::{arith_kernels, wcoj_kernels, CudaKernelProvider, ARITH_MODULE, WCOJ_MODULE};
8use crate::device_runtime::StreamId;
9use crate::launch::LaunchRecorder;
10use crate::memory::{CudaColumn, DeviceMemoryView, DeviceRead, TrackedCudaSlice};
11use crate::wcoj_metadata::{
12 Wcoj4CycleRootAggValue, WcojCycle4HgWorkPlanU32, WcojCycle4HgWorkPlanU64, WcojRelationMetadata,
13 WcojRootAggValue, WcojTriangleHgCountPhaseU32, WcojTriangleHgWorkPlanU32,
14 WcojTriangleHgWorkPlanU64,
15};
16use crate::{AsKernelParam, CudaBuffer, LaunchAsync, LaunchConfig};
17
18const BLOCK_SIZE: u32 = 256;
19const HG_COUNT_BLOCK_SIZE: u32 = 512;
20
21impl CudaKernelProvider {
22 pub fn wcoj_build_metadata_u32_recorded(
23 &self,
24 input: &CudaBuffer,
25 key_col_idx: usize,
26 launch_stream: StreamId,
27 ) -> Result<WcojRelationMetadata<u32>> {
28 self.validate_metadata_column(input, key_col_idx, MetadataWidth::U32)?;
29 let keys = &metadata_column_u32(input, key_col_idx)?;
30 self.build_metadata_u32_from_column(input, key_col_idx, keys, launch_stream)
31 }
32
33 pub fn wcoj_build_metadata_u64_recorded(
34 &self,
35 input: &CudaBuffer,
36 key_col_idx: usize,
37 launch_stream: StreamId,
38 ) -> Result<WcojRelationMetadata<u64>> {
39 self.validate_metadata_column(input, key_col_idx, MetadataWidth::U64)?;
40 let keys = &metadata_column_u64(input, key_col_idx)?;
41 self.build_metadata_u64_from_column(input, key_col_idx, keys, launch_stream)
42 }
43
44 pub fn wcoj_triangle_hg_work_plan_u32_recorded(
45 &self,
46 e_xy: &CudaBuffer,
47 e_yz: &CudaBuffer,
48 e_xz: &CudaBuffer,
49 block_work_unit: u32,
50 launch_stream: StreamId,
51 ) -> Result<WcojTriangleHgWorkPlanU32> {
52 let ctx = "wcoj_triangle_hg_work_plan_u32_recorded";
53 if block_work_unit == 0 {
54 return Err(XlogError::Kernel(format!(
55 "{ctx}: block_work_unit must be nonzero"
56 )));
57 }
58 validate_binary_u32(ctx, "e_xy", e_xy)?;
59 validate_binary_u32(ctx, "e_yz", e_yz)?;
60 validate_binary_u32(ctx, "e_xz", e_xz)?;
61
62 let n_xy = self.metadata_logical_rows(e_xy)?;
63 let n_yz = self.metadata_logical_rows(e_yz)?;
64 let n_xz = self.metadata_logical_rows(e_xz)?;
65 let prefix_len = n_xy
66 .checked_add(1)
67 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: prefix length overflow")))?;
68 let mut xy_work_prefix = self.memory().alloc::<u32>(prefix_len as usize)?;
69 let mut xy_yz_start = self.memory().alloc::<u32>(n_xy as usize)?;
70 let mut xy_yz_end = self.memory().alloc::<u32>(n_xy as usize)?;
71 let mut xy_xz_start = self.memory().alloc::<u32>(n_xy as usize)?;
72 let mut xy_xz_end = self.memory().alloc::<u32>(n_xy as usize)?;
73
74 if n_xy == 0 {
75 let block_counts = self.memory().alloc::<u32>(1)?;
76 let block_offsets = self.memory().alloc::<u32>(1)?;
77 let scratch_x = self.memory().alloc::<u32>(1)?;
78 let scratch_y = self.memory().alloc::<u32>(1)?;
79 let scratch_z = self.memory().alloc::<u32>(1)?;
80 return Ok(WcojTriangleHgWorkPlanU32 {
81 xy_work_prefix,
82 xy_yz_start,
83 xy_yz_end,
84 xy_xz_start,
85 xy_xz_end,
86 block_counts,
87 block_offsets,
88 scratch_x,
89 scratch_y,
90 scratch_z,
91 total_work: 0,
92 block_work_unit,
93 row_count: 0,
94 });
95 }
96
97 let runtime = self.memory().runtime().ok_or_else(|| {
98 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
99 })?;
100 let cu_stream = runtime
101 .stream_pool()
102 .resolve(launch_stream)
103 .ok_or_else(|| {
104 XlogError::Kernel(format!(
105 "{ctx}: launch_stream StreamId({}) does not resolve",
106 launch_stream.0
107 ))
108 })?;
109
110 let xy_col0 = &metadata_column_u32(e_xy, 0)?;
111 let xy_col1 = &metadata_column_u32(e_xy, 1)?;
112 let yz_col0 = &metadata_column_u32(e_yz, 0)?;
113 let xz_col0 = &metadata_column_u32(e_xz, 0)?;
114
115 let mut scan_scratch = self.multiblock_scan_u32_scratch_for_len(prefix_len)?;
116 let mut rec = LaunchRecorder::new_strict(launch_stream);
117 rec.read(e_xy.num_rows_device());
118 rec.read(e_yz.num_rows_device());
119 rec.read(e_xz.num_rows_device());
120 rec.read_column(e_xy.column(0).expect("xy.col0"));
121 rec.read_column(e_xy.column(1).expect("xy.col1"));
122 rec.read_column(e_yz.column(0).expect("yz.col0"));
123 rec.read_column(e_xz.column(0).expect("xz.col0"));
124 rec.write(&xy_work_prefix);
125 rec.write(&xy_yz_start);
126 rec.write(&xy_yz_end);
127 rec.write(&xy_xz_start);
128 rec.write(&xy_xz_end);
129 for level in scan_scratch.levels() {
130 rec.read_write(level);
131 }
132 rec.preflight(runtime)
133 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
134 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
135 let kernel = self
136 .device()
137 .inner()
138 .get_func(
139 WCOJ_MODULE,
140 wcoj_kernels::WCOJ_TRIANGLE_BUILD_HG_WORK_PLAN_U32,
141 )
142 .ok_or_else(|| {
143 XlogError::Kernel(
144 "wcoj_triangle_build_hg_work_plan_u32 kernel not found".to_string(),
145 )
146 })?;
147 let grid = n_xy.div_ceil(BLOCK_SIZE);
148 unsafe {
149 kernel
150 .clone()
151 .launch_in(
152 stream,
153 LaunchConfig {
154 grid_dim: (grid, 1, 1),
155 block_dim: (BLOCK_SIZE, 1, 1),
156 shared_mem_bytes: 0,
157 },
158 (
159 xy_col0,
160 xy_col1,
161 n_xy,
162 yz_col0,
163 n_yz,
164 xz_col0,
165 n_xz,
166 &mut xy_work_prefix,
167 &mut xy_yz_start,
168 &mut xy_yz_end,
169 &mut xy_xz_start,
170 &mut xy_xz_end,
171 ),
172 )
173 .map_err(|e| {
174 XlogError::Kernel(format!(
175 "wcoj_triangle_build_hg_work_plan_u32 launch failed: {e}"
176 ))
177 })?;
178 }
179 self.multiblock_scan_u32_inplace_on_stream(
180 &mut xy_work_prefix,
181 prefix_len,
182 stream,
183 &mut scan_scratch,
184 )?;
185 Ok(())
186 };
187 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
189 .map_err(|error| error.into_xlog_error())?;
190
191 rec.commit()
192 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
193 cu_stream
194 .synchronize()
195 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
196 let total_work = self.dtoh_scalar_untracked::<u32>(&xy_work_prefix, n_xy as usize)?;
197 let scratch_slots = if total_work == 0 {
198 1usize
199 } else {
200 let grid = total_work.div_ceil(block_work_unit);
201 (grid as usize)
202 .checked_mul(block_work_unit as usize)
203 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: scratch slot overflow")))?
204 };
205 let scratch_x = self.memory().alloc::<u32>(scratch_slots)?;
206 let scratch_y = self.memory().alloc::<u32>(scratch_slots)?;
207 let scratch_z = self.memory().alloc::<u32>(scratch_slots)?;
208 let grid = if total_work == 0 {
209 1
210 } else {
211 total_work.div_ceil(block_work_unit)
212 };
213 let block_counts = self.memory().alloc::<u32>(grid as usize)?;
214 let block_offsets = self.memory().alloc::<u32>(grid as usize)?;
215
216 Ok(WcojTriangleHgWorkPlanU32 {
217 xy_work_prefix,
218 xy_yz_start,
219 xy_yz_end,
220 xy_xz_start,
221 xy_xz_end,
222 block_counts,
223 block_offsets,
224 scratch_x,
225 scratch_y,
226 scratch_z,
227 total_work,
228 block_work_unit,
229 row_count: n_xy,
230 })
231 }
232
233 pub fn wcoj_triangle_count_hg_u32_recorded(
234 &self,
235 e_yz: &CudaBuffer,
236 e_xz: &CudaBuffer,
237 plan: &WcojTriangleHgWorkPlanU32,
238 launch_stream: StreamId,
239 ) -> Result<CudaBuffer> {
240 let ctx = "wcoj_triangle_count_hg_u32_recorded";
241 validate_binary_u32(ctx, "e_yz", e_yz)?;
242 validate_binary_u32(ctx, "e_xz", e_xz)?;
243 let n_yz = self.metadata_logical_rows(e_yz)?;
244 let n_xz = self.metadata_logical_rows(e_xz)?;
245 let grid = if plan.total_work == 0 {
246 1
247 } else {
248 plan.total_work.div_ceil(plan.block_work_unit)
249 };
250 let bytes_count = (grid as usize)
251 .checked_mul(std::mem::size_of::<u32>())
252 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: count byte size overflow")))?;
253 let mut count_bytes = self.memory().alloc::<u8>(bytes_count)?;
254 let d_num_rows = self.memory().alloc::<u32>(1)?;
255
256 let runtime = self.memory().runtime().ok_or_else(|| {
257 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
258 })?;
259 let cu_stream = runtime
260 .stream_pool()
261 .resolve(launch_stream)
262 .ok_or_else(|| {
263 XlogError::Kernel(format!(
264 "{ctx}: launch_stream StreamId({}) does not resolve",
265 launch_stream.0
266 ))
267 })?;
268 let yz_col1 = &metadata_column_u32(e_yz, 1)?;
269 let xz_col1 = &metadata_column_u32(e_xz, 1)?;
270
271 let mut rec = LaunchRecorder::new_strict(launch_stream);
272 rec.read(e_yz.num_rows_device());
273 rec.read(e_xz.num_rows_device());
274 rec.read_column(e_yz.column(1).expect("yz.col1"));
275 rec.read_column(e_xz.column(1).expect("xz.col1"));
276 rec.read(&plan.xy_work_prefix);
277 rec.read(&plan.xy_yz_start);
278 rec.read(&plan.xy_yz_end);
279 rec.read(&plan.xy_xz_start);
280 rec.read(&plan.xy_xz_end);
281 rec.write(&count_bytes);
282 rec.write(&d_num_rows);
283 rec.preflight(runtime)
284 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
285 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
286 self.initialize_launch_metadata_u32(
287 grid,
288 &d_num_rows,
289 stream,
290 &format!("{ctx}: d_num_rows"),
291 )?;
292
293 let kernel = self
294 .device()
295 .inner()
296 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_TRIANGLE_COUNT_HG_U32)
297 .ok_or_else(|| {
298 XlogError::Kernel("wcoj_triangle_count_hg_u32 kernel not found".to_string())
299 })?;
300 let count_u32 = &mut reinterpret_u8_as_u32(&mut count_bytes)?;
301 let mut params: Vec<*mut c_void> = vec![
302 yz_col1.as_kernel_param(),
303 n_yz.as_kernel_param(),
304 xz_col1.as_kernel_param(),
305 n_xz.as_kernel_param(),
306 (&plan.xy_work_prefix).as_kernel_param(),
307 (&plan.xy_yz_start).as_kernel_param(),
308 (&plan.xy_yz_end).as_kernel_param(),
309 (&plan.xy_xz_start).as_kernel_param(),
310 (&plan.xy_xz_end).as_kernel_param(),
311 plan.row_count.as_kernel_param(),
312 plan.total_work.as_kernel_param(),
313 plan.block_work_unit.as_kernel_param(),
314 count_u32.as_kernel_param(),
315 ];
316 unsafe {
317 kernel
318 .clone()
319 .launch_in(
320 stream,
321 LaunchConfig {
322 grid_dim: (grid, 1, 1),
323 block_dim: (BLOCK_SIZE, 1, 1),
324 shared_mem_bytes: 0,
325 },
326 &mut params,
327 )
328 .map_err(|e| XlogError::Kernel(format!("{ctx}: launch failed: {e}")))?;
329 }
330 Ok(())
331 };
332 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
334 .map_err(|error| error.into_xlog_error())?;
335
336 rec.commit()
337 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
338 cu_stream
339 .synchronize()
340 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
341
342 let schema = Schema::new(vec![("count".to_string(), ScalarType::U32)]);
343 Ok(CudaBuffer::from_columns_with_host_count(
344 vec![count_bytes.into()],
345 grid as u64,
346 d_num_rows,
347 schema,
348 grid,
349 ))
350 }
351
352 pub fn wcoj_triangle_hg_u32_recorded(
353 &self,
354 e_xy: &CudaBuffer,
355 e_yz: &CudaBuffer,
356 e_xz: &CudaBuffer,
357 block_work_unit: u32,
358 launch_stream: StreamId,
359 ) -> Result<CudaBuffer> {
360 let ctx = "wcoj_triangle_hg_u32_recorded";
361 validate_binary_u32(ctx, "e_xy", e_xy)?;
362 validate_binary_u32(ctx, "e_yz", e_yz)?;
363 validate_binary_u32(ctx, "e_xz", e_xz)?;
364 let plan = self.wcoj_triangle_hg_work_plan_u32_recorded(
365 e_xy,
366 e_yz,
367 e_xz,
368 block_work_unit,
369 launch_stream,
370 )?;
371 self.wcoj_triangle_hg_u32_with_plan_recorded(e_xy, e_yz, e_xz, &plan, launch_stream)
372 }
373
374 pub fn wcoj_triangle_groupby_root_count_u32_recorded(
398 &self,
399 e_xy: &CudaBuffer,
400 e_yz: &CudaBuffer,
401 e_xz: &CudaBuffer,
402 block_work_unit: u32,
403 launch_stream: StreamId,
404 ) -> Result<CudaBuffer> {
405 let ctx = "wcoj_triangle_groupby_root_count_u32_recorded";
406 let e_xy = &self.wcoj_layout_u32_recorded(e_xy, launch_stream)?;
412 let e_yz = &self.wcoj_layout_u32_recorded(e_yz, launch_stream)?;
413 let e_xz = &self.wcoj_layout_u32_recorded(e_xz, launch_stream)?;
414 validate_binary_u32(ctx, "e_xy", e_xy)?;
415 validate_binary_u32(ctx, "e_yz", e_yz)?;
416 validate_binary_u32(ctx, "e_xz", e_xz)?;
417 let plan = self.wcoj_triangle_hg_work_plan_u32_recorded(
418 e_xy,
419 e_yz,
420 e_xz,
421 block_work_unit,
422 launch_stream,
423 )?;
424 let n_xy = plan.row_count;
425 let x_type = e_xy.schema().column_type(0).expect("xy.col0 type");
426 let out_schema = Schema::new(vec![
427 ("x".to_string(), x_type),
428 ("count".to_string(), ScalarType::U64),
429 ]);
430 if n_xy == 0 || plan.total_work == 0 {
431 return self.create_empty_buffer(out_schema);
432 }
433
434 let runtime = self.memory().runtime().ok_or_else(|| {
435 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
436 })?;
437 let cu_stream = runtime
438 .stream_pool()
439 .resolve(launch_stream)
440 .ok_or_else(|| {
441 XlogError::Kernel(format!(
442 "{ctx}: launch_stream StreamId({}) does not resolve",
443 launch_stream.0
444 ))
445 })?;
446
447 let yz_col1 = &metadata_column_u32(e_yz, 1)?;
448 let xz_col1 = &metadata_column_u32(e_xz, 1)?;
449 let n_yz = self.metadata_logical_rows(e_yz)?;
450 let n_xz = self.metadata_logical_rows(e_xz)?;
451
452 let mut row_counts = self
456 .memory()
457 .alloc::<u8>(n_xy as usize * std::mem::size_of::<u32>())?;
458 self.device()
459 .inner()
460 .memset_zeros(&mut row_counts)
461 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row counts failed: {e}")))?;
462
463 let grid = plan.total_work.div_ceil(plan.block_work_unit);
464 let mut rec = LaunchRecorder::new_strict(launch_stream);
465 rec.read(e_xy.num_rows_device());
466 rec.read(e_yz.num_rows_device());
467 rec.read(e_xz.num_rows_device());
468 rec.read_column(e_yz.column(1).expect("yz.col1"));
469 rec.read_column(e_xz.column(1).expect("xz.col1"));
470 rec.read(&plan.xy_work_prefix);
471 rec.read(&plan.xy_yz_start);
472 rec.read(&plan.xy_yz_end);
473 rec.read(&plan.xy_xz_start);
474 rec.read(&plan.xy_xz_end);
475 rec.write(&row_counts);
476 rec.preflight(runtime)
477 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
478 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
479 {
480 let kernel = self
481 .device()
482 .inner()
483 .get_func(
484 WCOJ_MODULE,
485 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_COUNT_HG_U32,
486 )
487 .ok_or_else(|| {
488 XlogError::Kernel(
489 "wcoj_triangle_groupby_root_count_hg_u32 kernel not found".to_string(),
490 )
491 })?;
492 let mut params: Vec<*mut c_void> = vec![
493 yz_col1.as_kernel_param(),
494 n_yz.as_kernel_param(),
495 xz_col1.as_kernel_param(),
496 n_xz.as_kernel_param(),
497 (&plan.xy_work_prefix).as_kernel_param(),
498 (&plan.xy_yz_start).as_kernel_param(),
499 (&plan.xy_yz_end).as_kernel_param(),
500 (&plan.xy_xz_start).as_kernel_param(),
501 (&plan.xy_xz_end).as_kernel_param(),
502 plan.row_count.as_kernel_param(),
503 plan.total_work.as_kernel_param(),
504 plan.block_work_unit.as_kernel_param(),
505 (&row_counts).as_kernel_param(),
506 ];
507 unsafe {
508 kernel
509 .clone()
510 .launch_in(
511 stream,
512 LaunchConfig {
513 grid_dim: (grid, 1, 1),
514 block_dim: (BLOCK_SIZE, 1, 1),
515 shared_mem_bytes: 0,
516 },
517 &mut params,
518 )
519 .map_err(|e| {
520 XlogError::Kernel(format!("{ctx}: groupby-count launch failed: {e}"))
521 })?;
522 }
523 }
524 Ok(())
525 };
526 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
528 .map_err(|error| error.into_xlog_error())?;
529
530 rec.commit()
531 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
532
533 let x_src = match e_xy.column(0).expect("xy.col0") {
537 CudaColumn::Owned(slice) => slice,
538 _ => {
539 return Err(XlogError::Kernel(format!(
540 "{ctx}: e_xy.col0 must be an owned CudaColumn"
541 )))
542 }
543 };
544 let x_copy = self
545 .memory()
546 .alloc::<u8>(n_xy as usize * std::mem::size_of::<u32>())?;
547 unsafe {
551 let res = sys::cuMemcpyDtoD_v2(
552 *x_copy.device_ptr(),
553 *x_src.device_ptr(),
554 n_xy as usize * std::mem::size_of::<u32>(),
555 );
556 if res != sys::cudaError_enum::CUDA_SUCCESS {
557 return Err(XlogError::Kernel(format!(
558 "{ctx}: copy X column failed: {res:?}"
559 )));
560 }
561 }
562 let mut d_num_rows = self.memory().alloc::<u32>(1)?;
563 self.device()
564 .inner()
565 .dtod_copy(e_xy.num_rows_device(), &mut d_num_rows)
566 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy row count failed: {e}")))?;
567 let staging_schema = Schema::new(vec![
568 ("x".to_string(), x_type),
569 ("count".to_string(), ScalarType::U32),
570 ]);
571 let staging = CudaBuffer::from_columns_with_host_count(
572 vec![x_copy.into(), row_counts.into()],
573 n_xy as u64,
574 d_num_rows,
575 staging_schema,
576 n_xy,
577 );
578
579 let mask = self.compare_const_mask_recorded::<u32>(
582 &staging,
583 1,
584 0u32,
585 crate::CompareOp::Gt,
586 launch_stream,
587 )?;
588 let compacted =
589 self.compact_buffer_by_device_mask_counted_recorded(&staging, &mask, launch_stream)?;
590 self.groupby_multi_agg_recorded(
591 &compacted,
592 &[0],
593 &[(1, xlog_core::AggOp::Sum)],
594 launch_stream,
595 )
596 }
597
598 #[expect(
630 clippy::too_many_arguments,
631 reason = "three relation inputs and launch controls mirror the recorded kernel contract"
632 )]
633 pub fn wcoj_triangle_groupby_root_agg_u32_recorded(
634 &self,
635 e_xy: &CudaBuffer,
636 e_yz: &CudaBuffer,
637 e_xz: &CudaBuffer,
638 agg_op: AggOp,
639 value: WcojRootAggValue,
640 block_work_unit: u32,
641 launch_stream: StreamId,
642 ) -> Result<CudaBuffer> {
643 let ctx = "wcoj_triangle_groupby_root_agg_u32_recorded";
644 let e_xy = &self.wcoj_layout_u32_recorded(e_xy, launch_stream)?;
650 let e_yz = &self.wcoj_layout_u32_recorded(e_yz, launch_stream)?;
651 let e_xz = &self.wcoj_layout_u32_recorded(e_xz, launch_stream)?;
652 let (kernel_name, agg_elem_size, agg_scalar, agg_name) = match agg_op {
653 AggOp::Sum => (
654 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_SUM_HG_U32,
655 std::mem::size_of::<u64>(),
656 ScalarType::U64,
657 "sum_0",
658 ),
659 AggOp::Min => (
660 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_MIN_HG_U32,
661 std::mem::size_of::<u32>(),
662 ScalarType::U32,
663 "min_0",
664 ),
665 AggOp::Max => (
666 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_MAX_HG_U32,
667 std::mem::size_of::<u32>(),
668 ScalarType::U32,
669 "max_0",
670 ),
671 other => {
672 return Err(XlogError::Kernel(format!(
673 "{ctx}: unsupported AggOp {other:?} (Sum/Min/Max only; use \
674 wcoj_triangle_groupby_root_count_u32_recorded for Count)"
675 )))
676 }
677 };
678 validate_binary_u32(ctx, "e_xy", e_xy)?;
679 validate_binary_u32(ctx, "e_yz", e_yz)?;
680 validate_binary_u32(ctx, "e_xz", e_xz)?;
681 let value_cols: &[(&CudaBuffer, &str)] = match value {
684 WcojRootAggValue::Y => &[(e_xy, "e_xy")],
685 WcojRootAggValue::Z => &[(e_yz, "e_yz"), (e_xz, "e_xz")],
686 };
687 for (buf, label) in value_cols {
688 let ty = buf.schema().column_type(1).expect("validated 2-col");
689 if ty != ScalarType::U32 {
690 return Err(XlogError::Kernel(format!(
691 "{ctx}: {label}.col1 supplies the aggregate value and must be U32, got {ty:?}"
692 )));
693 }
694 }
695
696 let plan = self.wcoj_triangle_hg_work_plan_u32_recorded(
697 e_xy,
698 e_yz,
699 e_xz,
700 block_work_unit,
701 launch_stream,
702 )?;
703 let n_xy = plan.row_count;
704 let x_type = e_xy.schema().column_type(0).expect("xy.col0 type");
705 let out_schema = Schema::new(vec![
706 ("x".to_string(), x_type),
707 (agg_name.to_string(), agg_scalar),
708 ]);
709 if n_xy == 0 || plan.total_work == 0 {
710 return self.create_empty_buffer(out_schema);
711 }
712
713 let runtime = self.memory().runtime().ok_or_else(|| {
714 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
715 })?;
716 let cu_stream = runtime
717 .stream_pool()
718 .resolve(launch_stream)
719 .ok_or_else(|| {
720 XlogError::Kernel(format!(
721 "{ctx}: launch_stream StreamId({}) does not resolve",
722 launch_stream.0
723 ))
724 })?;
725
726 let yz_col1 = &metadata_column_u32(e_yz, 1)?;
727 let xz_col1 = &metadata_column_u32(e_xz, 1)?;
728 let xy_col1 = &metadata_column_u32(e_xy, 1)?;
729 let n_yz = self.metadata_logical_rows(e_yz)?;
730 let n_xz = self.metadata_logical_rows(e_xz)?;
731 let value_from_z: u32 = match value {
732 WcojRootAggValue::Y => 0,
733 WcojRootAggValue::Z => 1,
734 };
735
736 let mut row_counts = self
740 .memory()
741 .alloc::<u8>(n_xy as usize * std::mem::size_of::<u32>())?;
742 self.device()
743 .inner()
744 .memset_zeros(&mut row_counts)
745 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row counts failed: {e}")))?;
746 let mut row_agg = self.memory().alloc::<u8>(n_xy as usize * agg_elem_size)?;
747 self.device()
748 .inner()
749 .memset_zeros(&mut row_agg)
750 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row aggregates failed: {e}")))?;
751
752 let grid = plan.total_work.div_ceil(plan.block_work_unit);
753 let mut rec = LaunchRecorder::new_strict(launch_stream);
754 rec.read(e_xy.num_rows_device());
755 rec.read(e_yz.num_rows_device());
756 rec.read(e_xz.num_rows_device());
757 rec.read_column(e_yz.column(1).expect("yz.col1"));
758 rec.read_column(e_xz.column(1).expect("xz.col1"));
759 rec.read_column(e_xy.column(1).expect("xy.col1"));
760 rec.read(&plan.xy_work_prefix);
761 rec.read(&plan.xy_yz_start);
762 rec.read(&plan.xy_yz_end);
763 rec.read(&plan.xy_xz_start);
764 rec.read(&plan.xy_xz_end);
765 rec.write(&row_counts);
766 rec.write(&row_agg);
767 rec.preflight(runtime)
768 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
769 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
770 if matches!(agg_op, AggOp::Min) {
771 let fill = self
773 .device()
774 .inner()
775 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U32)
776 .ok_or_else(|| {
777 XlogError::Kernel("arith_fill_const_u32 kernel not found".to_string())
778 })?;
779 let row_agg_u32 = &mut reinterpret_u8_as_u32(&mut row_agg)?;
780 unsafe {
782 fill.clone()
783 .launch_in(
784 stream,
785 LaunchConfig::for_num_elems(n_xy),
786 (u32::MAX, n_xy, &mut *row_agg_u32),
787 )
788 .map_err(|e| {
789 XlogError::Kernel(format!("{ctx}: min identity fill failed: {e}"))
790 })?;
791 }
792 }
793 {
794 let kernel = self
795 .device()
796 .inner()
797 .get_func(WCOJ_MODULE, kernel_name)
798 .ok_or_else(|| XlogError::Kernel(format!("{kernel_name} kernel not found")))?;
799 let mut params: Vec<*mut c_void> = vec![
800 yz_col1.as_kernel_param(),
801 n_yz.as_kernel_param(),
802 xz_col1.as_kernel_param(),
803 n_xz.as_kernel_param(),
804 xy_col1.as_kernel_param(),
805 value_from_z.as_kernel_param(),
806 (&plan.xy_work_prefix).as_kernel_param(),
807 (&plan.xy_yz_start).as_kernel_param(),
808 (&plan.xy_yz_end).as_kernel_param(),
809 (&plan.xy_xz_start).as_kernel_param(),
810 (&plan.xy_xz_end).as_kernel_param(),
811 plan.row_count.as_kernel_param(),
812 plan.total_work.as_kernel_param(),
813 plan.block_work_unit.as_kernel_param(),
814 (&row_counts).as_kernel_param(),
815 (&row_agg).as_kernel_param(),
816 ];
817 unsafe {
818 kernel
819 .clone()
820 .launch_in(
821 stream,
822 LaunchConfig {
823 grid_dim: (grid, 1, 1),
824 block_dim: (BLOCK_SIZE, 1, 1),
825 shared_mem_bytes: 0,
826 },
827 &mut params,
828 )
829 .map_err(|e| {
830 XlogError::Kernel(format!("{ctx}: groupby-agg launch failed: {e}"))
831 })?;
832 }
833 }
834 Ok(())
835 };
836 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
838 .map_err(|error| error.into_xlog_error())?;
839
840 rec.commit()
841 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
842
843 let x_src = match e_xy.column(0).expect("xy.col0") {
847 CudaColumn::Owned(slice) => slice,
848 _ => {
849 return Err(XlogError::Kernel(format!(
850 "{ctx}: e_xy.col0 must be an owned CudaColumn"
851 )))
852 }
853 };
854 let x_copy = self
855 .memory()
856 .alloc::<u8>(n_xy as usize * std::mem::size_of::<u32>())?;
857 unsafe {
861 let res = sys::cuMemcpyDtoD_v2(
862 *x_copy.device_ptr(),
863 *x_src.device_ptr(),
864 n_xy as usize * std::mem::size_of::<u32>(),
865 );
866 if res != sys::cudaError_enum::CUDA_SUCCESS {
867 return Err(XlogError::Kernel(format!(
868 "{ctx}: copy X column failed: {res:?}"
869 )));
870 }
871 }
872 let mut d_num_rows = self.memory().alloc::<u32>(1)?;
873 self.device()
874 .inner()
875 .dtod_copy(e_xy.num_rows_device(), &mut d_num_rows)
876 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy row count failed: {e}")))?;
877 let staging_schema = Schema::new(vec![
878 ("x".to_string(), x_type),
879 ("count".to_string(), ScalarType::U32),
880 ("agg".to_string(), agg_scalar),
881 ]);
882 let staging = CudaBuffer::from_columns_with_host_count(
883 vec![x_copy.into(), row_counts.into(), row_agg.into()],
884 n_xy as u64,
885 d_num_rows,
886 staging_schema,
887 n_xy,
888 );
889
890 let mask = self.compare_const_mask_recorded::<u32>(
893 &staging,
894 1,
895 0u32,
896 crate::CompareOp::Gt,
897 launch_stream,
898 )?;
899 let compacted =
900 self.compact_buffer_by_device_mask_counted_recorded(&staging, &mask, launch_stream)?;
901 self.groupby_multi_agg_recorded(&compacted, &[0], &[(2, agg_op)], launch_stream)
902 }
903
904 pub fn wcoj_triangle_groupby_root_count_u64_recorded(
925 &self,
926 e_xy: &CudaBuffer,
927 e_yz: &CudaBuffer,
928 e_xz: &CudaBuffer,
929 block_work_unit: u32,
930 launch_stream: StreamId,
931 ) -> Result<CudaBuffer> {
932 let ctx = "wcoj_triangle_groupby_root_count_u64_recorded";
933 let e_xy = &self.wcoj_layout_u64_recorded(e_xy, launch_stream)?;
939 let e_yz = &self.wcoj_layout_u64_recorded(e_yz, launch_stream)?;
940 let e_xz = &self.wcoj_layout_u64_recorded(e_xz, launch_stream)?;
941 validate_binary_u64(ctx, "e_xy", e_xy)?;
942 validate_binary_u64(ctx, "e_yz", e_yz)?;
943 validate_binary_u64(ctx, "e_xz", e_xz)?;
944 let plan = self.wcoj_triangle_hg_work_plan_u64_recorded(
945 e_xy,
946 e_yz,
947 e_xz,
948 block_work_unit,
949 launch_stream,
950 )?;
951 let n_xy = plan.row_count;
952 let out_schema = Schema::new(vec![
953 ("x".to_string(), ScalarType::U64),
954 ("count".to_string(), ScalarType::U64),
955 ]);
956 if n_xy == 0 || plan.total_work == 0 {
957 return self.create_empty_buffer(out_schema);
958 }
959
960 let runtime = self.memory().runtime().ok_or_else(|| {
961 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
962 })?;
963 let cu_stream = runtime
964 .stream_pool()
965 .resolve(launch_stream)
966 .ok_or_else(|| {
967 XlogError::Kernel(format!(
968 "{ctx}: launch_stream StreamId({}) does not resolve",
969 launch_stream.0
970 ))
971 })?;
972
973 let yz_col1 = &metadata_column_u64(e_yz, 1)?;
974 let xz_col1 = &metadata_column_u64(e_xz, 1)?;
975 let n_yz = self.metadata_logical_rows(e_yz)?;
976 let n_xz = self.metadata_logical_rows(e_xz)?;
977
978 let mut row_counts = self.memory().alloc::<u32>(n_xy as usize)?;
980 self.device()
981 .inner()
982 .memset_zeros(&mut row_counts)
983 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row counts failed: {e}")))?;
984
985 let grid = plan.total_work.div_ceil(plan.block_work_unit);
986 let mut rec = LaunchRecorder::new_strict(launch_stream);
987 rec.read(e_xy.num_rows_device());
988 rec.read(e_yz.num_rows_device());
989 rec.read(e_xz.num_rows_device());
990 rec.read_column(e_yz.column(1).expect("yz.col1"));
991 rec.read_column(e_xz.column(1).expect("xz.col1"));
992 rec.read(&plan.xy_work_prefix);
993 rec.read(&plan.xy_yz_start);
994 rec.read(&plan.xy_yz_end);
995 rec.read(&plan.xy_xz_start);
996 rec.read(&plan.xy_xz_end);
997 rec.write(&row_counts);
998 rec.preflight(runtime)
999 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
1000 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1001 {
1002 let kernel = self
1003 .device()
1004 .inner()
1005 .get_func(
1006 WCOJ_MODULE,
1007 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_COUNT_HG_U64,
1008 )
1009 .ok_or_else(|| {
1010 XlogError::Kernel(
1011 "wcoj_triangle_groupby_root_count_hg_u64 kernel not found".to_string(),
1012 )
1013 })?;
1014 let mut params: Vec<*mut c_void> = vec![
1015 yz_col1.as_kernel_param(),
1016 n_yz.as_kernel_param(),
1017 xz_col1.as_kernel_param(),
1018 n_xz.as_kernel_param(),
1019 (&plan.xy_work_prefix).as_kernel_param(),
1020 (&plan.xy_yz_start).as_kernel_param(),
1021 (&plan.xy_yz_end).as_kernel_param(),
1022 (&plan.xy_xz_start).as_kernel_param(),
1023 (&plan.xy_xz_end).as_kernel_param(),
1024 plan.row_count.as_kernel_param(),
1025 plan.total_work.as_kernel_param(),
1026 plan.block_work_unit.as_kernel_param(),
1027 (&row_counts).as_kernel_param(),
1028 ];
1029 unsafe {
1030 kernel
1031 .clone()
1032 .launch_in(
1033 stream,
1034 LaunchConfig {
1035 grid_dim: (grid, 1, 1),
1036 block_dim: (BLOCK_SIZE, 1, 1),
1037 shared_mem_bytes: 0,
1038 },
1039 &mut params,
1040 )
1041 .map_err(|e| {
1042 XlogError::Kernel(format!("{ctx}: groupby-count launch failed: {e}"))
1043 })?;
1044 }
1045 }
1046 Ok(())
1047 };
1048 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
1050 .map_err(|error| error.into_xlog_error())?;
1051
1052 rec.commit()
1053 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
1054
1055 let meta = self.wcoj_build_metadata_u64_recorded(e_xy, 0, launch_stream)?;
1059 let key_count = meta.key_count;
1060 if key_count == 0 {
1061 return self.create_empty_buffer(out_schema);
1062 }
1063 let mut sums = self
1064 .memory()
1065 .alloc::<u8>(key_count as usize * std::mem::size_of::<u64>())?;
1066 self.device()
1067 .inner()
1068 .memset_zeros(&mut sums)
1069 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero group sums failed: {e}")))?;
1070
1071 let mut rec_sum = LaunchRecorder::new_strict(launch_stream);
1072 rec_sum.read(&row_counts);
1073 rec_sum.read(&meta.prefix_sum);
1074 rec_sum.write(&sums);
1075 rec_sum
1076 .preflight(runtime)
1077 .map_err(|e| XlogError::Kernel(format!("{ctx}: reduce preflight failed: {e}")))?;
1078 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1079 {
1080 let kernel = self
1081 .device()
1082 .inner()
1083 .get_func(
1084 WCOJ_MODULE,
1085 wcoj_kernels::WCOJ_GROUPBY_ROOT_SEGMENT_SUM_COUNTS_U32,
1086 )
1087 .ok_or_else(|| {
1088 XlogError::Kernel(
1089 "wcoj_groupby_root_segment_sum_counts_u32 kernel not found".to_string(),
1090 )
1091 })?;
1092 let reduce_grid = n_xy.div_ceil(BLOCK_SIZE);
1093 let mut params: Vec<*mut c_void> = vec![
1094 (&row_counts).as_kernel_param(),
1095 n_xy.as_kernel_param(),
1096 (&meta.prefix_sum).as_kernel_param(),
1097 key_count.as_kernel_param(),
1098 (&sums).as_kernel_param(),
1099 ];
1100 unsafe {
1101 kernel
1102 .clone()
1103 .launch_in(
1104 stream,
1105 LaunchConfig {
1106 grid_dim: (reduce_grid, 1, 1),
1107 block_dim: (BLOCK_SIZE, 1, 1),
1108 shared_mem_bytes: 0,
1109 },
1110 &mut params,
1111 )
1112 .map_err(|e| {
1113 XlogError::Kernel(format!("{ctx}: reduce launch failed: {e}"))
1114 })?;
1115 }
1116 }
1117 Ok(())
1118 };
1119 let rec_sum = unsafe { rec_sum.enqueue_prepared_with(&cu_stream, enqueue) }
1121 .map_err(|error| error.into_xlog_error())?;
1122
1123 rec_sum
1124 .commit()
1125 .map_err(|e| XlogError::Kernel(format!("{ctx}: reduce commit failed: {e}")))?;
1126
1127 let x_copy = self
1133 .memory()
1134 .alloc::<u8>(key_count as usize * std::mem::size_of::<u64>())?;
1135 let d_num_rows = self.memory().alloc::<u32>(1)?;
1136 let mut rec_copy = LaunchRecorder::new_strict(launch_stream);
1137 rec_copy.read(&meta.unique_keys);
1138 rec_copy.write(&x_copy);
1139 rec_copy.write(&d_num_rows);
1140 rec_copy
1141 .preflight(runtime)
1142 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy preflight failed: {e}")))?;
1143 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1144 unsafe {
1145 let res = sys::cuMemcpyDtoDAsync_v2(
1146 *x_copy.device_ptr(),
1147 *meta.unique_keys.device_ptr(),
1148 key_count as usize * std::mem::size_of::<u64>(),
1149 stream.stream().cu_stream(),
1150 );
1151 if res != sys::cudaError_enum::CUDA_SUCCESS {
1152 return Err(XlogError::Kernel(format!(
1153 "{ctx}: DtoD unique keys copy failed: {res:?}"
1154 )));
1155 }
1156 }
1157 self.initialize_launch_metadata_u32(
1158 key_count,
1159 &d_num_rows,
1160 stream,
1161 &format!("{ctx}: d_num_rows"),
1162 )?;
1163 Ok(())
1164 };
1165 let rec_copy = unsafe { rec_copy.enqueue_prepared_with(&cu_stream, enqueue) }
1167 .map_err(|error| error.into_xlog_error())?;
1168
1169 rec_copy
1170 .commit()
1171 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy commit failed: {e}")))?;
1172 cu_stream
1173 .synchronize()
1174 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
1175 let staging_schema = Schema::new(vec![
1176 ("x".to_string(), ScalarType::U64),
1177 ("count".to_string(), ScalarType::U64),
1178 ]);
1179 let staging = CudaBuffer::from_columns_with_host_count(
1180 vec![x_copy.into(), sums.into()],
1181 u64::from(key_count),
1182 d_num_rows,
1183 staging_schema,
1184 key_count,
1185 );
1186 let mask = self.compare_const_mask_recorded::<u64>(
1187 &staging,
1188 1,
1189 0u64,
1190 crate::CompareOp::Gt,
1191 launch_stream,
1192 )?;
1193 self.compact_buffer_by_device_mask_counted_recorded(&staging, &mask, launch_stream)
1194 }
1195
1196 #[expect(
1229 clippy::too_many_arguments,
1230 reason = "three relation inputs and launch controls mirror the recorded kernel contract"
1231 )]
1232 pub fn wcoj_triangle_groupby_root_agg_u64_recorded(
1233 &self,
1234 e_xy: &CudaBuffer,
1235 e_yz: &CudaBuffer,
1236 e_xz: &CudaBuffer,
1237 agg_op: AggOp,
1238 value: WcojRootAggValue,
1239 block_work_unit: u32,
1240 launch_stream: StreamId,
1241 ) -> Result<CudaBuffer> {
1242 let ctx = "wcoj_triangle_groupby_root_agg_u64_recorded";
1243 let e_xy = &self.wcoj_layout_u64_recorded(e_xy, launch_stream)?;
1249 let e_yz = &self.wcoj_layout_u64_recorded(e_yz, launch_stream)?;
1250 let e_xz = &self.wcoj_layout_u64_recorded(e_xz, launch_stream)?;
1251 let (kernel_name, segment_kernel_name, agg_name) = match agg_op {
1252 AggOp::Sum => (
1253 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_SUM_HG_U64,
1254 wcoj_kernels::WCOJ_GROUPBY_ROOT_SEGMENT_SUM_VALUES_U64,
1255 "sum_0",
1256 ),
1257 AggOp::Min => (
1258 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_MIN_HG_U64,
1259 wcoj_kernels::WCOJ_GROUPBY_ROOT_SEGMENT_MIN_VALUES_U64,
1260 "min_0",
1261 ),
1262 AggOp::Max => (
1263 wcoj_kernels::WCOJ_TRIANGLE_GROUPBY_ROOT_MAX_HG_U64,
1264 wcoj_kernels::WCOJ_GROUPBY_ROOT_SEGMENT_MAX_VALUES_U64,
1265 "max_0",
1266 ),
1267 other => {
1268 return Err(XlogError::Kernel(format!(
1269 "{ctx}: unsupported AggOp {other:?} (Sum/Min/Max only; use \
1270 wcoj_triangle_groupby_root_count_u64_recorded for Count)"
1271 )))
1272 }
1273 };
1274 validate_binary_u64(ctx, "e_xy", e_xy)?;
1275 validate_binary_u64(ctx, "e_yz", e_yz)?;
1276 validate_binary_u64(ctx, "e_xz", e_xz)?;
1277 let plan = self.wcoj_triangle_hg_work_plan_u64_recorded(
1278 e_xy,
1279 e_yz,
1280 e_xz,
1281 block_work_unit,
1282 launch_stream,
1283 )?;
1284 let n_xy = plan.row_count;
1285 let out_schema = Schema::new(vec![
1286 ("x".to_string(), ScalarType::U64),
1287 (agg_name.to_string(), ScalarType::U64),
1288 ]);
1289 if n_xy == 0 || plan.total_work == 0 {
1290 return self.create_empty_buffer(out_schema);
1291 }
1292
1293 let runtime = self.memory().runtime().ok_or_else(|| {
1294 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
1295 })?;
1296 let cu_stream = runtime
1297 .stream_pool()
1298 .resolve(launch_stream)
1299 .ok_or_else(|| {
1300 XlogError::Kernel(format!(
1301 "{ctx}: launch_stream StreamId({}) does not resolve",
1302 launch_stream.0
1303 ))
1304 })?;
1305
1306 let yz_col1 = &metadata_column_u64(e_yz, 1)?;
1307 let xz_col1 = &metadata_column_u64(e_xz, 1)?;
1308 let xy_col1 = &metadata_column_u64(e_xy, 1)?;
1309 let n_yz = self.metadata_logical_rows(e_yz)?;
1310 let n_xz = self.metadata_logical_rows(e_xz)?;
1311 let value_from_z: u32 = match value {
1312 WcojRootAggValue::Y => 0,
1313 WcojRootAggValue::Z => 1,
1314 };
1315
1316 let mut row_counts = self.memory().alloc::<u32>(n_xy as usize)?;
1318 self.device()
1319 .inner()
1320 .memset_zeros(&mut row_counts)
1321 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row counts failed: {e}")))?;
1322 let mut row_agg = self
1323 .memory()
1324 .alloc::<u8>(n_xy as usize * std::mem::size_of::<u64>())?;
1325 self.device()
1326 .inner()
1327 .memset_zeros(&mut row_agg)
1328 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row aggregates failed: {e}")))?;
1329
1330 let grid = plan.total_work.div_ceil(plan.block_work_unit);
1331 let mut rec = LaunchRecorder::new_strict(launch_stream);
1332 rec.read(e_xy.num_rows_device());
1333 rec.read(e_yz.num_rows_device());
1334 rec.read(e_xz.num_rows_device());
1335 rec.read_column(e_yz.column(1).expect("yz.col1"));
1336 rec.read_column(e_xz.column(1).expect("xz.col1"));
1337 rec.read_column(e_xy.column(1).expect("xy.col1"));
1338 rec.read(&plan.xy_work_prefix);
1339 rec.read(&plan.xy_yz_start);
1340 rec.read(&plan.xy_yz_end);
1341 rec.read(&plan.xy_xz_start);
1342 rec.read(&plan.xy_xz_end);
1343 rec.write(&row_counts);
1344 rec.write(&row_agg);
1345 rec.preflight(runtime)
1346 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
1347 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1348 if matches!(agg_op, AggOp::Min) {
1349 let fill = self
1351 .device()
1352 .inner()
1353 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U64)
1354 .ok_or_else(|| {
1355 XlogError::Kernel("arith_fill_const_u64 kernel not found".to_string())
1356 })?;
1357 let row_agg_u64 = &mut reinterpret_u8_as_u64(&mut row_agg)?;
1358 unsafe {
1360 fill.clone()
1361 .launch_in(
1362 stream,
1363 LaunchConfig::for_num_elems(n_xy),
1364 (u64::MAX, n_xy, &mut *row_agg_u64),
1365 )
1366 .map_err(|e| {
1367 XlogError::Kernel(format!("{ctx}: min identity fill failed: {e}"))
1368 })?;
1369 }
1370 }
1371 {
1372 let kernel = self
1373 .device()
1374 .inner()
1375 .get_func(WCOJ_MODULE, kernel_name)
1376 .ok_or_else(|| XlogError::Kernel(format!("{kernel_name} kernel not found")))?;
1377 let mut params: Vec<*mut c_void> = vec![
1378 yz_col1.as_kernel_param(),
1379 n_yz.as_kernel_param(),
1380 xz_col1.as_kernel_param(),
1381 n_xz.as_kernel_param(),
1382 xy_col1.as_kernel_param(),
1383 value_from_z.as_kernel_param(),
1384 (&plan.xy_work_prefix).as_kernel_param(),
1385 (&plan.xy_yz_start).as_kernel_param(),
1386 (&plan.xy_yz_end).as_kernel_param(),
1387 (&plan.xy_xz_start).as_kernel_param(),
1388 (&plan.xy_xz_end).as_kernel_param(),
1389 plan.row_count.as_kernel_param(),
1390 plan.total_work.as_kernel_param(),
1391 plan.block_work_unit.as_kernel_param(),
1392 (&row_counts).as_kernel_param(),
1393 (&row_agg).as_kernel_param(),
1394 ];
1395 unsafe {
1396 kernel
1397 .clone()
1398 .launch_in(
1399 stream,
1400 LaunchConfig {
1401 grid_dim: (grid, 1, 1),
1402 block_dim: (BLOCK_SIZE, 1, 1),
1403 shared_mem_bytes: 0,
1404 },
1405 &mut params,
1406 )
1407 .map_err(|e| {
1408 XlogError::Kernel(format!("{ctx}: groupby-agg launch failed: {e}"))
1409 })?;
1410 }
1411 }
1412 Ok(())
1413 };
1414 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
1416 .map_err(|error| error.into_xlog_error())?;
1417
1418 rec.commit()
1419 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
1420
1421 let meta = self.wcoj_build_metadata_u64_recorded(e_xy, 0, launch_stream)?;
1425 let key_count = meta.key_count;
1426 if key_count == 0 {
1427 return self.create_empty_buffer(out_schema);
1428 }
1429 let mut count_sums = self
1430 .memory()
1431 .alloc::<u8>(key_count as usize * std::mem::size_of::<u64>())?;
1432 self.device()
1433 .inner()
1434 .memset_zeros(&mut count_sums)
1435 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero group counts failed: {e}")))?;
1436 let mut group_agg = self
1437 .memory()
1438 .alloc::<u8>(key_count as usize * std::mem::size_of::<u64>())?;
1439 self.device()
1440 .inner()
1441 .memset_zeros(&mut group_agg)
1442 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero group aggregates failed: {e}")))?;
1443
1444 let mut rec_reduce = LaunchRecorder::new_strict(launch_stream);
1445 rec_reduce.read(&row_counts);
1446 rec_reduce.read(&row_agg);
1447 rec_reduce.read(&meta.prefix_sum);
1448 rec_reduce.write(&count_sums);
1449 rec_reduce.write(&group_agg);
1450 rec_reduce
1451 .preflight(runtime)
1452 .map_err(|e| XlogError::Kernel(format!("{ctx}: reduce preflight failed: {e}")))?;
1453 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1454 if matches!(agg_op, AggOp::Min) {
1455 let fill = self
1456 .device()
1457 .inner()
1458 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U64)
1459 .ok_or_else(|| {
1460 XlogError::Kernel("arith_fill_const_u64 kernel not found".to_string())
1461 })?;
1462 let group_agg_u64 = &mut reinterpret_u8_as_u64(&mut group_agg)?;
1463 unsafe {
1465 fill.clone()
1466 .launch_in(
1467 stream,
1468 LaunchConfig::for_num_elems(key_count),
1469 (u64::MAX, key_count, &mut *group_agg_u64),
1470 )
1471 .map_err(|e| {
1472 XlogError::Kernel(format!("{ctx}: group min identity fill failed: {e}"))
1473 })?;
1474 }
1475 }
1476 let reduce_grid = n_xy.div_ceil(BLOCK_SIZE);
1477 {
1478 let kernel = self
1479 .device()
1480 .inner()
1481 .get_func(
1482 WCOJ_MODULE,
1483 wcoj_kernels::WCOJ_GROUPBY_ROOT_SEGMENT_SUM_COUNTS_U32,
1484 )
1485 .ok_or_else(|| {
1486 XlogError::Kernel(
1487 "wcoj_groupby_root_segment_sum_counts_u32 kernel not found".to_string(),
1488 )
1489 })?;
1490 let mut params: Vec<*mut c_void> = vec![
1491 (&row_counts).as_kernel_param(),
1492 n_xy.as_kernel_param(),
1493 (&meta.prefix_sum).as_kernel_param(),
1494 key_count.as_kernel_param(),
1495 (&count_sums).as_kernel_param(),
1496 ];
1497 unsafe {
1498 kernel
1499 .clone()
1500 .launch_in(
1501 stream,
1502 LaunchConfig {
1503 grid_dim: (reduce_grid, 1, 1),
1504 block_dim: (BLOCK_SIZE, 1, 1),
1505 shared_mem_bytes: 0,
1506 },
1507 &mut params,
1508 )
1509 .map_err(|e| {
1510 XlogError::Kernel(format!("{ctx}: count reduce launch failed: {e}"))
1511 })?;
1512 }
1513 }
1514 {
1515 let kernel = self
1516 .device()
1517 .inner()
1518 .get_func(WCOJ_MODULE, segment_kernel_name)
1519 .ok_or_else(|| {
1520 XlogError::Kernel(format!("{segment_kernel_name} kernel not found"))
1521 })?;
1522 let mut params: Vec<*mut c_void> = vec![
1523 (&row_counts).as_kernel_param(),
1524 (&row_agg).as_kernel_param(),
1525 n_xy.as_kernel_param(),
1526 (&meta.prefix_sum).as_kernel_param(),
1527 key_count.as_kernel_param(),
1528 (&group_agg).as_kernel_param(),
1529 ];
1530 unsafe {
1531 kernel
1532 .clone()
1533 .launch_in(
1534 stream,
1535 LaunchConfig {
1536 grid_dim: (reduce_grid, 1, 1),
1537 block_dim: (BLOCK_SIZE, 1, 1),
1538 shared_mem_bytes: 0,
1539 },
1540 &mut params,
1541 )
1542 .map_err(|e| {
1543 XlogError::Kernel(format!("{ctx}: agg reduce launch failed: {e}"))
1544 })?;
1545 }
1546 }
1547 Ok(())
1548 };
1549 let rec_reduce = unsafe { rec_reduce.enqueue_prepared_with(&cu_stream, enqueue) }
1551 .map_err(|error| error.into_xlog_error())?;
1552
1553 rec_reduce
1554 .commit()
1555 .map_err(|e| XlogError::Kernel(format!("{ctx}: reduce commit failed: {e}")))?;
1556
1557 let x_copy = self
1563 .memory()
1564 .alloc::<u8>(key_count as usize * std::mem::size_of::<u64>())?;
1565 let d_num_rows_agg = self.memory().alloc::<u32>(1)?;
1566 let d_num_rows_counts = self.memory().alloc::<u32>(1)?;
1567 let mut rec_copy = LaunchRecorder::new_strict(launch_stream);
1568 rec_copy.read(&meta.unique_keys);
1569 rec_copy.write(&x_copy);
1570 rec_copy.write(&d_num_rows_agg);
1571 rec_copy.write(&d_num_rows_counts);
1572 rec_copy
1573 .preflight(runtime)
1574 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy preflight failed: {e}")))?;
1575 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1576 unsafe {
1577 let res = sys::cuMemcpyDtoDAsync_v2(
1578 *x_copy.device_ptr(),
1579 *meta.unique_keys.device_ptr(),
1580 key_count as usize * std::mem::size_of::<u64>(),
1581 stream.stream().cu_stream(),
1582 );
1583 if res != sys::cudaError_enum::CUDA_SUCCESS {
1584 return Err(XlogError::Kernel(format!(
1585 "{ctx}: DtoD unique keys copy failed: {res:?}"
1586 )));
1587 }
1588 }
1589 self.initialize_launch_metadata_u32(
1590 key_count,
1591 &d_num_rows_agg,
1592 stream,
1593 &format!("{ctx}: d_num_rows_agg"),
1594 )?;
1595 self.initialize_launch_metadata_u32(
1596 key_count,
1597 &d_num_rows_counts,
1598 stream,
1599 &format!("{ctx}: d_num_rows_counts"),
1600 )?;
1601 Ok(())
1602 };
1603 let rec_copy = unsafe { rec_copy.enqueue_prepared_with(&cu_stream, enqueue) }
1605 .map_err(|error| error.into_xlog_error())?;
1606
1607 rec_copy
1608 .commit()
1609 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy commit failed: {e}")))?;
1610 cu_stream
1611 .synchronize()
1612 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
1613
1614 let counts_schema = Schema::new(vec![("count".to_string(), ScalarType::U64)]);
1615 let counts_buf = CudaBuffer::from_columns_with_host_count(
1616 vec![count_sums.into()],
1617 u64::from(key_count),
1618 d_num_rows_counts,
1619 counts_schema,
1620 key_count,
1621 );
1622 let staging = CudaBuffer::from_columns_with_host_count(
1623 vec![x_copy.into(), group_agg.into()],
1624 u64::from(key_count),
1625 d_num_rows_agg,
1626 out_schema,
1627 key_count,
1628 );
1629 let mask = self.compare_const_mask_recorded::<u64>(
1630 &counts_buf,
1631 0,
1632 0u64,
1633 crate::CompareOp::Gt,
1634 launch_stream,
1635 )?;
1636 self.compact_buffer_by_device_mask_counted_recorded(&staging, &mask, launch_stream)
1637 }
1638
1639 pub fn wcoj_triangle_hg_count_phase_u32_recorded(
1640 &self,
1641 e_xy: &CudaBuffer,
1642 e_yz: &CudaBuffer,
1643 e_xz: &CudaBuffer,
1644 plan: &WcojTriangleHgWorkPlanU32,
1645 launch_stream: StreamId,
1646 ) -> Result<WcojTriangleHgCountPhaseU32> {
1647 let ctx = "wcoj_triangle_hg_count_phase_u32_recorded";
1648 validate_binary_u32(ctx, "e_xy", e_xy)?;
1649 validate_binary_u32(ctx, "e_yz", e_yz)?;
1650 validate_binary_u32(ctx, "e_xz", e_xz)?;
1651 let grid = plan.total_work.div_ceil(plan.block_work_unit);
1652 if grid > 1024 {
1653 return Err(XlogError::Kernel(format!(
1654 "{ctx}: spike phase path requires grid <= 1024, got {grid}"
1655 )));
1656 }
1657 let total_rows_device = self.memory().alloc::<u32>(1)?;
1658 if plan.total_work == 0 {
1659 return Ok(WcojTriangleHgCountPhaseU32 {
1660 total_rows_device,
1661 total_rows: 0,
1662 });
1663 }
1664
1665 let runtime = self.memory().runtime().ok_or_else(|| {
1666 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
1667 })?;
1668 let cu_stream = runtime
1669 .stream_pool()
1670 .resolve(launch_stream)
1671 .ok_or_else(|| {
1672 XlogError::Kernel(format!(
1673 "{ctx}: launch_stream StreamId({}) does not resolve",
1674 launch_stream.0
1675 ))
1676 })?;
1677
1678 let yz_col1 = &metadata_column_u32(e_yz, 1)?;
1679 let xz_col1 = &metadata_column_u32(e_xz, 1)?;
1680 let n_yz = self.metadata_logical_rows(e_yz)?;
1681 let n_xz = self.metadata_logical_rows(e_xz)?;
1682
1683 let mut rec_hg = LaunchRecorder::new_strict(launch_stream);
1684 rec_hg.read(e_xy.num_rows_device());
1685 rec_hg.read(e_yz.num_rows_device());
1686 rec_hg.read(e_xz.num_rows_device());
1687 rec_hg.read_column(e_yz.column(1).expect("yz.col1"));
1688 rec_hg.read_column(e_xz.column(1).expect("xz.col1"));
1689 rec_hg.read(&plan.xy_work_prefix);
1690 rec_hg.read(&plan.xy_yz_start);
1691 rec_hg.read(&plan.xy_yz_end);
1692 rec_hg.read(&plan.xy_xz_start);
1693 rec_hg.read(&plan.xy_xz_end);
1694 rec_hg.read_write(&plan.block_counts);
1695 rec_hg.read_write(&plan.block_offsets);
1696 rec_hg.write(&total_rows_device);
1697 rec_hg
1698 .preflight(runtime)
1699 .map_err(|e| XlogError::Kernel(format!("{ctx}: count preflight failed: {e}")))?;
1700 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1701 {
1702 let kernel = self
1703 .device()
1704 .inner()
1705 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_TRIANGLE_COUNT_HG_U32)
1706 .ok_or_else(|| {
1707 XlogError::Kernel("wcoj_triangle_count_hg_u32 kernel not found".to_string())
1708 })?;
1709 let mut params: Vec<*mut c_void> = vec![
1710 yz_col1.as_kernel_param(),
1711 n_yz.as_kernel_param(),
1712 xz_col1.as_kernel_param(),
1713 n_xz.as_kernel_param(),
1714 (&plan.xy_work_prefix).as_kernel_param(),
1715 (&plan.xy_yz_start).as_kernel_param(),
1716 (&plan.xy_yz_end).as_kernel_param(),
1717 (&plan.xy_xz_start).as_kernel_param(),
1718 (&plan.xy_xz_end).as_kernel_param(),
1719 plan.row_count.as_kernel_param(),
1720 plan.total_work.as_kernel_param(),
1721 plan.block_work_unit.as_kernel_param(),
1722 (&plan.block_counts).as_kernel_param(),
1723 ];
1724 unsafe {
1725 kernel
1726 .clone()
1727 .launch_in(
1728 stream,
1729 LaunchConfig {
1730 grid_dim: (grid, 1, 1),
1731 block_dim: (BLOCK_SIZE, 1, 1),
1732 shared_mem_bytes: 0,
1733 },
1734 &mut params,
1735 )
1736 .map_err(|e| {
1737 XlogError::Kernel(format!("{ctx}: count launch failed: {e}"))
1738 })?;
1739 }
1740 }
1741 {
1742 let kernel = self
1743 .device()
1744 .inner()
1745 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_SCAN_HG_BLOCK_COUNTS_U32)
1746 .ok_or_else(|| {
1747 XlogError::Kernel(
1748 "wcoj_scan_hg_block_counts_u32 kernel not found".to_string(),
1749 )
1750 })?;
1751 let mut params: Vec<*mut c_void> = vec![
1752 (&plan.block_counts).as_kernel_param(),
1753 grid.as_kernel_param(),
1754 (&plan.block_offsets).as_kernel_param(),
1755 (&total_rows_device).as_kernel_param(),
1756 ];
1757 unsafe {
1758 kernel
1759 .clone()
1760 .launch_in(
1761 stream,
1762 LaunchConfig {
1763 grid_dim: (1, 1, 1),
1764 block_dim: (1024, 1, 1),
1765 shared_mem_bytes: 0,
1766 },
1767 &mut params,
1768 )
1769 .map_err(|e| {
1770 XlogError::Kernel(format!("{ctx}: scan launch failed: {e}"))
1771 })?;
1772 }
1773 }
1774 Ok(())
1775 };
1776 let rec_hg = unsafe { rec_hg.enqueue_prepared_with(&cu_stream, enqueue) }
1778 .map_err(|error| error.into_xlog_error())?;
1779
1780 rec_hg
1781 .commit()
1782 .map_err(|e| XlogError::Kernel(format!("{ctx}: count commit failed: {e}")))?;
1783 cu_stream
1784 .synchronize()
1785 .map_err(|e| XlogError::Kernel(format!("{ctx}: count stream sync failed: {e}")))?;
1786 let total_rows = self
1787 .dtoh_scalar_untracked::<u32>(&total_rows_device, 0)
1788 .map_err(|e| XlogError::Kernel(format!("{ctx}: read total rows failed: {e}")))?;
1789 Ok(WcojTriangleHgCountPhaseU32 {
1790 total_rows_device,
1791 total_rows,
1792 })
1793 }
1794
1795 pub fn wcoj_triangle_hg_materialize_phase_u32_recorded(
1796 &self,
1797 e_xy: &CudaBuffer,
1798 e_yz: &CudaBuffer,
1799 e_xz: &CudaBuffer,
1800 plan: &WcojTriangleHgWorkPlanU32,
1801 count: WcojTriangleHgCountPhaseU32,
1802 launch_stream: StreamId,
1803 ) -> Result<CudaBuffer> {
1804 let ctx = "wcoj_triangle_hg_materialize_phase_u32_recorded";
1805 validate_binary_u32(ctx, "e_xy", e_xy)?;
1806 validate_binary_u32(ctx, "e_yz", e_yz)?;
1807 validate_binary_u32(ctx, "e_xz", e_xz)?;
1808 let out_schema = Schema::new(vec![
1809 (
1810 "x".to_string(),
1811 e_xy.schema().column_type(0).expect("xy.col0 type"),
1812 ),
1813 (
1814 "y".to_string(),
1815 e_xy.schema().column_type(1).expect("xy.col1 type"),
1816 ),
1817 (
1818 "z".to_string(),
1819 e_yz.schema().column_type(1).expect("yz.col1 type"),
1820 ),
1821 ]);
1822 if count.total_rows == 0 {
1823 return self.create_empty_buffer(out_schema);
1824 }
1825 let grid = plan.total_work.div_ceil(plan.block_work_unit);
1826 if grid > 1024 {
1827 return Err(XlogError::Kernel(format!(
1828 "{ctx}: spike phase path requires grid <= 1024, got {grid}"
1829 )));
1830 }
1831 let runtime = self.memory().runtime().ok_or_else(|| {
1832 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
1833 })?;
1834 let cu_stream = runtime
1835 .stream_pool()
1836 .resolve(launch_stream)
1837 .ok_or_else(|| {
1838 XlogError::Kernel(format!(
1839 "{ctx}: launch_stream StreamId({}) does not resolve",
1840 launch_stream.0
1841 ))
1842 })?;
1843 let xy_col0 = &metadata_column_u32(e_xy, 0)?;
1844 let xy_col1 = &metadata_column_u32(e_xy, 1)?;
1845 let yz_col1 = &metadata_column_u32(e_yz, 1)?;
1846 let xz_col1 = &metadata_column_u32(e_xz, 1)?;
1847 let n_yz = self.metadata_logical_rows(e_yz)?;
1848 let n_xz = self.metadata_logical_rows(e_xz)?;
1849 let bytes_per_col = (count.total_rows as usize)
1850 .checked_mul(std::mem::size_of::<u32>())
1851 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: output byte size overflow")))?;
1852 let mut out_x = self.memory().alloc::<u8>(bytes_per_col)?;
1853 let mut out_y = self.memory().alloc::<u8>(bytes_per_col)?;
1854 let mut out_z = self.memory().alloc::<u8>(bytes_per_col)?;
1855 let mut rec_mat = LaunchRecorder::new_strict(launch_stream);
1856 rec_mat.read(e_xy.num_rows_device());
1857 rec_mat.read(e_yz.num_rows_device());
1858 rec_mat.read(e_xz.num_rows_device());
1859 rec_mat.read_column(e_xy.column(0).expect("xy.col0"));
1860 rec_mat.read_column(e_xy.column(1).expect("xy.col1"));
1861 rec_mat.read_column(e_yz.column(1).expect("yz.col1"));
1862 rec_mat.read_column(e_xz.column(1).expect("xz.col1"));
1863 rec_mat.read(&plan.xy_work_prefix);
1864 rec_mat.read(&plan.xy_yz_start);
1865 rec_mat.read(&plan.xy_yz_end);
1866 rec_mat.read(&plan.xy_xz_start);
1867 rec_mat.read(&plan.xy_xz_end);
1868 rec_mat.read(&plan.block_offsets);
1869 rec_mat.write(&out_x);
1870 rec_mat.write(&out_y);
1871 rec_mat.write(&out_z);
1872 rec_mat
1873 .preflight(runtime)
1874 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize preflight failed: {e}")))?;
1875 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1876 {
1877 let kernel = self
1878 .device()
1879 .inner()
1880 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_TRIANGLE_MATERIALIZE_HG_U32)
1881 .ok_or_else(|| {
1882 XlogError::Kernel(
1883 "wcoj_triangle_materialize_hg_u32 kernel not found".to_string(),
1884 )
1885 })?;
1886 let out_x_u32 = &mut reinterpret_u8_as_u32(&mut out_x)?;
1887 let out_y_u32 = &mut reinterpret_u8_as_u32(&mut out_y)?;
1888 let out_z_u32 = &mut reinterpret_u8_as_u32(&mut out_z)?;
1889 let mut params: Vec<*mut c_void> = vec![
1890 xy_col0.as_kernel_param(),
1891 xy_col1.as_kernel_param(),
1892 yz_col1.as_kernel_param(),
1893 n_yz.as_kernel_param(),
1894 xz_col1.as_kernel_param(),
1895 n_xz.as_kernel_param(),
1896 (&plan.xy_work_prefix).as_kernel_param(),
1897 (&plan.xy_yz_start).as_kernel_param(),
1898 (&plan.xy_yz_end).as_kernel_param(),
1899 (&plan.xy_xz_start).as_kernel_param(),
1900 (&plan.xy_xz_end).as_kernel_param(),
1901 plan.row_count.as_kernel_param(),
1902 plan.total_work.as_kernel_param(),
1903 plan.block_work_unit.as_kernel_param(),
1904 (&plan.block_offsets).as_kernel_param(),
1905 count.total_rows.as_kernel_param(),
1906 out_x_u32.as_kernel_param(),
1907 out_y_u32.as_kernel_param(),
1908 out_z_u32.as_kernel_param(),
1909 ];
1910 unsafe {
1911 kernel
1912 .clone()
1913 .launch_in(
1914 stream,
1915 LaunchConfig {
1916 grid_dim: (grid, 1, 1),
1917 block_dim: (BLOCK_SIZE, 1, 1),
1918 shared_mem_bytes: 0,
1919 },
1920 &mut params,
1921 )
1922 .map_err(|e| {
1923 XlogError::Kernel(format!("{ctx}: materialize launch failed: {e}"))
1924 })?;
1925 }
1926 }
1927 Ok(())
1928 };
1929 let rec_mat = unsafe { rec_mat.enqueue_prepared_with(&cu_stream, enqueue) }
1931 .map_err(|error| error.into_xlog_error())?;
1932
1933 rec_mat
1934 .commit()
1935 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize commit failed: {e}")))?;
1936 cu_stream.synchronize().map_err(|e| {
1937 XlogError::Kernel(format!("{ctx}: materialize stream sync failed: {e}"))
1938 })?;
1939 Ok(CudaBuffer::from_columns_with_host_count(
1940 vec![out_x.into(), out_y.into(), out_z.into()],
1941 count.total_rows as u64,
1942 count.total_rows_device,
1943 out_schema,
1944 count.total_rows,
1945 ))
1946 }
1947
1948 pub fn wcoj_triangle_hg_u32_with_plan_recorded(
1949 &self,
1950 e_xy: &CudaBuffer,
1951 e_yz: &CudaBuffer,
1952 e_xz: &CudaBuffer,
1953 plan: &WcojTriangleHgWorkPlanU32,
1954 launch_stream: StreamId,
1955 ) -> Result<CudaBuffer> {
1956 let ctx = "wcoj_triangle_hg_u32_with_plan_recorded";
1957 validate_binary_u32(ctx, "e_xy", e_xy)?;
1958 validate_binary_u32(ctx, "e_yz", e_yz)?;
1959 validate_binary_u32(ctx, "e_xz", e_xz)?;
1960 let out_schema = Schema::new(vec![
1961 (
1962 "x".to_string(),
1963 e_xy.schema().column_type(0).expect("xy.col0 type"),
1964 ),
1965 (
1966 "y".to_string(),
1967 e_xy.schema().column_type(1).expect("xy.col1 type"),
1968 ),
1969 (
1970 "z".to_string(),
1971 e_yz.schema().column_type(1).expect("yz.col1 type"),
1972 ),
1973 ]);
1974 if plan.total_work == 0 {
1975 return self.create_empty_buffer(out_schema);
1976 }
1977
1978 let grid = plan.total_work.div_ceil(plan.block_work_unit);
1979 let bytes_count = (grid as usize)
1980 .checked_mul(std::mem::size_of::<u32>())
1981 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: count byte size overflow")))?;
1982 let mut local_counts = None;
1983 let mut local_offsets = None;
1984 if grid > 1024 {
1985 local_counts = Some(self.memory().alloc::<u32>(grid as usize)?);
1986 local_offsets = Some(self.memory().alloc::<u32>(grid as usize)?);
1987 }
1988 let total_rows_device = self.memory().alloc::<u32>(1)?;
1989
1990 let runtime = self.memory().runtime().ok_or_else(|| {
1991 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
1992 })?;
1993 let cu_stream = runtime
1994 .stream_pool()
1995 .resolve(launch_stream)
1996 .ok_or_else(|| {
1997 XlogError::Kernel(format!(
1998 "{ctx}: launch_stream StreamId({}) does not resolve",
1999 launch_stream.0
2000 ))
2001 })?;
2002
2003 let xy_col0 = &metadata_column_u32(e_xy, 0)?;
2004 let xy_col1 = &metadata_column_u32(e_xy, 1)?;
2005 let yz_col1 = &metadata_column_u32(e_yz, 1)?;
2006 let xz_col1 = &metadata_column_u32(e_xz, 1)?;
2007 let n_yz = self.metadata_logical_rows(e_yz)?;
2008 let n_xz = self.metadata_logical_rows(e_xz)?;
2009
2010 let count_u32 = if grid <= 1024 {
2011 &plan.block_counts
2012 } else {
2013 local_counts
2014 .as_ref()
2015 .expect("local HG counts allocated when grid exceeds single-block scan")
2016 };
2017 let mut scan_scratch =
2018 self.multiblock_scan_u32_scratch_for_len(if grid > 1024 { grid } else { 0 })?;
2019 let mut rec_hg = LaunchRecorder::new_strict(launch_stream);
2020 rec_hg.read(e_xy.num_rows_device());
2021 rec_hg.read(e_yz.num_rows_device());
2022 rec_hg.read(e_xz.num_rows_device());
2023 rec_hg.read_column(e_xy.column(0).expect("xy.col0"));
2024 rec_hg.read_column(e_xy.column(1).expect("xy.col1"));
2025 rec_hg.read_column(e_yz.column(1).expect("yz.col1"));
2026 rec_hg.read_column(e_xz.column(1).expect("xz.col1"));
2027 rec_hg.read(&plan.xy_work_prefix);
2028 rec_hg.read(&plan.xy_yz_start);
2029 rec_hg.read(&plan.xy_yz_end);
2030 rec_hg.read(&plan.xy_xz_start);
2031 rec_hg.read(&plan.xy_xz_end);
2032 rec_hg.read_write(count_u32);
2033 if grid <= 1024 {
2034 rec_hg.read_write(&plan.block_offsets);
2035 } else {
2036 rec_hg.read_write(
2037 local_offsets
2038 .as_ref()
2039 .expect("local HG offsets allocated when grid exceeds single-block scan"),
2040 );
2041 }
2042 rec_hg.write(&total_rows_device);
2043 rec_hg.read_write(&plan.scratch_x);
2044 rec_hg.read_write(&plan.scratch_y);
2045 rec_hg.read_write(&plan.scratch_z);
2046 for level in scan_scratch.levels() {
2047 rec_hg.read_write(level);
2048 }
2049 rec_hg
2050 .preflight(runtime)
2051 .map_err(|e| XlogError::Kernel(format!("{ctx}: HG preflight failed: {e}")))?;
2052 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
2053 {
2054 let kernel = self
2055 .device()
2056 .inner()
2057 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_TRIANGLE_COUNT_HG_CACHED_U32)
2058 .ok_or_else(|| {
2059 XlogError::Kernel(
2060 "wcoj_triangle_count_hg_cached_u32 kernel not found".to_string(),
2061 )
2062 })?;
2063 let mut params: Vec<*mut c_void> = vec![
2064 xy_col0.as_kernel_param(),
2065 xy_col1.as_kernel_param(),
2066 yz_col1.as_kernel_param(),
2067 n_yz.as_kernel_param(),
2068 xz_col1.as_kernel_param(),
2069 n_xz.as_kernel_param(),
2070 (&plan.xy_work_prefix).as_kernel_param(),
2071 (&plan.xy_yz_start).as_kernel_param(),
2072 (&plan.xy_yz_end).as_kernel_param(),
2073 (&plan.xy_xz_start).as_kernel_param(),
2074 (&plan.xy_xz_end).as_kernel_param(),
2075 plan.row_count.as_kernel_param(),
2076 plan.total_work.as_kernel_param(),
2077 plan.block_work_unit.as_kernel_param(),
2078 count_u32.as_kernel_param(),
2079 (&plan.scratch_x).as_kernel_param(),
2080 (&plan.scratch_y).as_kernel_param(),
2081 (&plan.scratch_z).as_kernel_param(),
2082 ];
2083 unsafe {
2084 kernel
2085 .clone()
2086 .launch_in(
2087 stream,
2088 LaunchConfig {
2089 grid_dim: (grid, 1, 1),
2090 block_dim: (HG_COUNT_BLOCK_SIZE, 1, 1),
2091 shared_mem_bytes: 0,
2092 },
2093 &mut params,
2094 )
2095 .map_err(|e| {
2096 XlogError::Kernel(format!("{ctx}: cached count launch failed: {e}"))
2097 })?;
2098 }
2099 }
2100 if grid <= 1024 {
2101 let kernel = self
2102 .device()
2103 .inner()
2104 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_SCAN_HG_BLOCK_COUNTS_U32)
2105 .ok_or_else(|| {
2106 XlogError::Kernel(
2107 "wcoj_scan_hg_block_counts_u32 kernel not found".to_string(),
2108 )
2109 })?;
2110 let mut params: Vec<*mut c_void> = vec![
2111 count_u32.as_kernel_param(),
2112 grid.as_kernel_param(),
2113 (&plan.block_offsets).as_kernel_param(),
2114 (&total_rows_device).as_kernel_param(),
2115 ];
2116 unsafe {
2117 kernel
2118 .clone()
2119 .launch_in(
2120 stream,
2121 LaunchConfig {
2122 grid_dim: (1, 1, 1),
2123 block_dim: (1024, 1, 1),
2124 shared_mem_bytes: 0,
2125 },
2126 &mut params,
2127 )
2128 .map_err(|e| {
2129 XlogError::Kernel(format!("{ctx}: HG block-count scan failed: {e}"))
2130 })?;
2131 }
2132 } else {
2133 let offsets_mut = local_offsets
2134 .as_mut()
2135 .expect("local HG offsets allocated when grid exceeds single-block scan");
2136 unsafe {
2137 let res = sys::cuMemcpyDtoDAsync_v2(
2138 *offsets_mut.device_ptr(),
2139 *count_u32.device_ptr(),
2140 bytes_count,
2141 stream.stream().cu_stream(),
2142 );
2143 if res != sys::cudaError_enum::CUDA_SUCCESS {
2144 return Err(XlogError::Kernel(format!(
2145 "{ctx}: DtoD count to offsets failed: {res:?}"
2146 )));
2147 }
2148 }
2149 self.multiblock_scan_u32_inplace_on_stream(
2150 offsets_mut,
2151 grid,
2152 stream,
2153 &mut scan_scratch,
2154 )?;
2155 let total_kernel = self
2156 .device()
2157 .inner()
2158 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_COMPUTE_TOTAL)
2159 .ok_or_else(|| {
2160 XlogError::Kernel("wcoj_compute_total kernel not found".to_string())
2161 })?;
2162 let mut params: Vec<*mut c_void> = vec![
2163 count_u32.as_kernel_param(),
2164 (&*offsets_mut).as_kernel_param(),
2165 grid.as_kernel_param(),
2166 (&total_rows_device).as_kernel_param(),
2167 ];
2168 unsafe {
2169 total_kernel
2170 .clone()
2171 .launch_in(
2172 stream,
2173 LaunchConfig {
2174 grid_dim: (1, 1, 1),
2175 block_dim: (1, 1, 1),
2176 shared_mem_bytes: 0,
2177 },
2178 &mut params,
2179 )
2180 .map_err(|e| {
2181 XlogError::Kernel(format!("{ctx}: HG total reducer failed: {e}"))
2182 })?;
2183 }
2184 }
2185 Ok(())
2186 };
2187 let rec_hg = unsafe { rec_hg.enqueue_prepared_with(&cu_stream, enqueue) }
2189 .map_err(|error| error.into_xlog_error())?;
2190
2191 rec_hg
2192 .commit()
2193 .map_err(|e| XlogError::Kernel(format!("{ctx}: HG count commit failed: {e}")))?;
2194 cu_stream
2195 .synchronize()
2196 .map_err(|e| XlogError::Kernel(format!("{ctx}: count stream sync failed: {e}")))?;
2197 let total_rows = self
2198 .dtoh_scalar_untracked::<u32>(&total_rows_device, 0)
2199 .map_err(|e| XlogError::Kernel(format!("{ctx}: read total rows failed: {e}")))?;
2200 if total_rows == 0 {
2201 return self.create_empty_buffer(out_schema);
2202 }
2203
2204 let bytes_per_col = (total_rows as usize)
2205 .checked_mul(std::mem::size_of::<u32>())
2206 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: output byte size overflow")))?;
2207 let mut out_x = self.memory().alloc::<u8>(bytes_per_col)?;
2208 let mut out_y = self.memory().alloc::<u8>(bytes_per_col)?;
2209 let mut out_z = self.memory().alloc::<u8>(bytes_per_col)?;
2210 let materialize_offsets = if grid <= 1024 {
2211 &plan.block_offsets
2212 } else {
2213 local_offsets
2214 .as_ref()
2215 .expect("local HG offsets allocated when grid exceeds single-block scan")
2216 };
2217 let mut rec_mat = LaunchRecorder::new_strict(launch_stream);
2218 rec_mat.read(count_u32);
2219 rec_mat.read(materialize_offsets);
2220 rec_mat.read(&plan.scratch_x);
2221 rec_mat.read(&plan.scratch_y);
2222 rec_mat.read(&plan.scratch_z);
2223 rec_mat.write(&out_x);
2224 rec_mat.write(&out_y);
2225 rec_mat.write(&out_z);
2226 rec_mat
2227 .preflight(runtime)
2228 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize preflight failed: {e}")))?;
2229 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
2230 {
2231 let kernel = self
2232 .device()
2233 .inner()
2234 .get_func(
2235 WCOJ_MODULE,
2236 wcoj_kernels::WCOJ_TRIANGLE_MATERIALIZE_HG_CACHED_U32,
2237 )
2238 .ok_or_else(|| {
2239 XlogError::Kernel(
2240 "wcoj_triangle_materialize_hg_cached_u32 kernel not found".to_string(),
2241 )
2242 })?;
2243 let out_x_u32 = &mut reinterpret_u8_as_u32(&mut out_x)?;
2244 let out_y_u32 = &mut reinterpret_u8_as_u32(&mut out_y)?;
2245 let out_z_u32 = &mut reinterpret_u8_as_u32(&mut out_z)?;
2246 let mut params: Vec<*mut c_void> = vec![
2247 count_u32.as_kernel_param(),
2248 materialize_offsets.as_kernel_param(),
2249 plan.block_work_unit.as_kernel_param(),
2250 total_rows.as_kernel_param(),
2251 (&plan.scratch_x).as_kernel_param(),
2252 (&plan.scratch_y).as_kernel_param(),
2253 (&plan.scratch_z).as_kernel_param(),
2254 out_x_u32.as_kernel_param(),
2255 out_y_u32.as_kernel_param(),
2256 out_z_u32.as_kernel_param(),
2257 ];
2258 unsafe {
2259 kernel
2260 .clone()
2261 .launch_in(
2262 stream,
2263 LaunchConfig {
2264 grid_dim: (grid, 1, 1),
2265 block_dim: (BLOCK_SIZE, 1, 1),
2266 shared_mem_bytes: 0,
2267 },
2268 &mut params,
2269 )
2270 .map_err(|e| {
2271 XlogError::Kernel(format!("{ctx}: materialize launch failed: {e}"))
2272 })?;
2273 }
2274 }
2275 Ok(())
2276 };
2277 let rec_mat = unsafe { rec_mat.enqueue_prepared_with(&cu_stream, enqueue) }
2279 .map_err(|error| error.into_xlog_error())?;
2280
2281 rec_mat
2282 .commit()
2283 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize commit failed: {e}")))?;
2284 cu_stream.synchronize().map_err(|e| {
2285 XlogError::Kernel(format!("{ctx}: materialize stream sync failed: {e}"))
2286 })?;
2287
2288 Ok(CudaBuffer::from_columns_with_host_count(
2289 vec![out_x.into(), out_y.into(), out_z.into()],
2290 total_rows as u64,
2291 total_rows_device,
2292 out_schema,
2293 total_rows,
2294 ))
2295 }
2296
2297 pub fn wcoj_triangle_hg_work_plan_u64_recorded(
2298 &self,
2299 e_xy: &CudaBuffer,
2300 e_yz: &CudaBuffer,
2301 e_xz: &CudaBuffer,
2302 block_work_unit: u32,
2303 launch_stream: StreamId,
2304 ) -> Result<WcojTriangleHgWorkPlanU64> {
2305 let ctx = "wcoj_triangle_hg_work_plan_u64_recorded";
2306 if block_work_unit == 0 {
2307 return Err(XlogError::Kernel(format!(
2308 "{ctx}: block_work_unit must be nonzero"
2309 )));
2310 }
2311 validate_binary_u64(ctx, "e_xy", e_xy)?;
2312 validate_binary_u64(ctx, "e_yz", e_yz)?;
2313 validate_binary_u64(ctx, "e_xz", e_xz)?;
2314
2315 let n_xy = self.metadata_logical_rows(e_xy)?;
2316 let n_yz = self.metadata_logical_rows(e_yz)?;
2317 let n_xz = self.metadata_logical_rows(e_xz)?;
2318 let prefix_len = n_xy
2319 .checked_add(1)
2320 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: prefix length overflow")))?;
2321 let mut xy_work_prefix = self.memory().alloc::<u32>(prefix_len as usize)?;
2322 let mut xy_yz_start = self.memory().alloc::<u32>(n_xy as usize)?;
2323 let mut xy_yz_end = self.memory().alloc::<u32>(n_xy as usize)?;
2324 let mut xy_xz_start = self.memory().alloc::<u32>(n_xy as usize)?;
2325 let mut xy_xz_end = self.memory().alloc::<u32>(n_xy as usize)?;
2326
2327 if n_xy == 0 {
2328 let block_counts = self.memory().alloc::<u32>(1)?;
2329 let block_offsets = self.memory().alloc::<u32>(1)?;
2330 return Ok(WcojTriangleHgWorkPlanU64 {
2331 xy_work_prefix,
2332 xy_yz_start,
2333 xy_yz_end,
2334 xy_xz_start,
2335 xy_xz_end,
2336 block_counts,
2337 block_offsets,
2338 total_work: 0,
2339 block_work_unit,
2340 row_count: n_xy,
2341 });
2342 }
2343
2344 let runtime = self.memory().runtime().ok_or_else(|| {
2345 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
2346 })?;
2347 let cu_stream = runtime
2348 .stream_pool()
2349 .resolve(launch_stream)
2350 .ok_or_else(|| {
2351 XlogError::Kernel(format!(
2352 "{ctx}: launch_stream StreamId({}) does not resolve",
2353 launch_stream.0
2354 ))
2355 })?;
2356
2357 let xy_col0 = &metadata_column_u64(e_xy, 0)?;
2358 let xy_col1 = &metadata_column_u64(e_xy, 1)?;
2359 let yz_col0 = &metadata_column_u64(e_yz, 0)?;
2360 let xz_col0 = &metadata_column_u64(e_xz, 0)?;
2361
2362 let mut scan_scratch = self.multiblock_scan_u32_scratch_for_len(prefix_len)?;
2363 let mut rec = LaunchRecorder::new_strict(launch_stream);
2364 rec.read(e_xy.num_rows_device());
2365 rec.read(e_yz.num_rows_device());
2366 rec.read(e_xz.num_rows_device());
2367 rec.read_column(e_xy.column(0).expect("xy.col0"));
2368 rec.read_column(e_xy.column(1).expect("xy.col1"));
2369 rec.read_column(e_yz.column(0).expect("yz.col0"));
2370 rec.read_column(e_xz.column(0).expect("xz.col0"));
2371 rec.write(&xy_work_prefix);
2372 rec.write(&xy_yz_start);
2373 rec.write(&xy_yz_end);
2374 rec.write(&xy_xz_start);
2375 rec.write(&xy_xz_end);
2376 for level in scan_scratch.levels() {
2377 rec.read_write(level);
2378 }
2379 rec.preflight(runtime)
2380 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
2381 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
2382 let kernel = self
2383 .device()
2384 .inner()
2385 .get_func(
2386 WCOJ_MODULE,
2387 wcoj_kernels::WCOJ_TRIANGLE_BUILD_HG_WORK_PLAN_U64,
2388 )
2389 .ok_or_else(|| {
2390 XlogError::Kernel(
2391 "wcoj_triangle_build_hg_work_plan_u64 kernel not found".to_string(),
2392 )
2393 })?;
2394 let grid = n_xy.div_ceil(BLOCK_SIZE);
2395 unsafe {
2396 kernel
2397 .clone()
2398 .launch_in(
2399 stream,
2400 LaunchConfig {
2401 grid_dim: (grid, 1, 1),
2402 block_dim: (BLOCK_SIZE, 1, 1),
2403 shared_mem_bytes: 0,
2404 },
2405 (
2406 xy_col0,
2407 xy_col1,
2408 n_xy,
2409 yz_col0,
2410 n_yz,
2411 xz_col0,
2412 n_xz,
2413 &mut xy_work_prefix,
2414 &mut xy_yz_start,
2415 &mut xy_yz_end,
2416 &mut xy_xz_start,
2417 &mut xy_xz_end,
2418 ),
2419 )
2420 .map_err(|e| {
2421 XlogError::Kernel(format!(
2422 "wcoj_triangle_build_hg_work_plan_u64 launch failed: {e}"
2423 ))
2424 })?;
2425 }
2426 self.multiblock_scan_u32_inplace_on_stream(
2427 &mut xy_work_prefix,
2428 prefix_len,
2429 stream,
2430 &mut scan_scratch,
2431 )?;
2432 Ok(())
2433 };
2434 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
2436 .map_err(|error| error.into_xlog_error())?;
2437
2438 rec.commit()
2439 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
2440 cu_stream
2441 .synchronize()
2442 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
2443 let total_work = self.dtoh_scalar_untracked::<u32>(&xy_work_prefix, n_xy as usize)?;
2444 let grid = if total_work == 0 {
2445 1
2446 } else {
2447 total_work.div_ceil(block_work_unit)
2448 };
2449 let block_counts = self.memory().alloc::<u32>(grid as usize)?;
2450 let block_offsets = self.memory().alloc::<u32>(grid as usize)?;
2451
2452 Ok(WcojTriangleHgWorkPlanU64 {
2453 xy_work_prefix,
2454 xy_yz_start,
2455 xy_yz_end,
2456 xy_xz_start,
2457 xy_xz_end,
2458 block_counts,
2459 block_offsets,
2460 total_work,
2461 block_work_unit,
2462 row_count: n_xy,
2463 })
2464 }
2465
2466 pub fn wcoj_triangle_hg_u64_recorded(
2467 &self,
2468 e_xy: &CudaBuffer,
2469 e_yz: &CudaBuffer,
2470 e_xz: &CudaBuffer,
2471 block_work_unit: u32,
2472 launch_stream: StreamId,
2473 ) -> Result<CudaBuffer> {
2474 let ctx = "wcoj_triangle_hg_u64_recorded";
2475 validate_binary_u64(ctx, "e_xy", e_xy)?;
2476 validate_binary_u64(ctx, "e_yz", e_yz)?;
2477 validate_binary_u64(ctx, "e_xz", e_xz)?;
2478 let plan = self.wcoj_triangle_hg_work_plan_u64_recorded(
2479 e_xy,
2480 e_yz,
2481 e_xz,
2482 block_work_unit,
2483 launch_stream,
2484 )?;
2485 let out_schema = Schema::new(vec![
2486 ("col0".to_string(), ScalarType::U64),
2487 ("col1".to_string(), ScalarType::U64),
2488 ("col2".to_string(), ScalarType::U64),
2489 ]);
2490 if plan.total_work == 0 {
2491 return self.create_empty_buffer(out_schema);
2492 }
2493
2494 let grid = plan.total_work.div_ceil(plan.block_work_unit);
2495 let bytes_count = (grid as usize)
2496 .checked_mul(std::mem::size_of::<u32>())
2497 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: count byte size overflow")))?;
2498 let mut local_counts = None;
2499 let mut local_offsets = None;
2500 if grid > 1024 {
2501 local_counts = Some(self.memory().alloc::<u32>(grid as usize)?);
2502 local_offsets = Some(self.memory().alloc::<u32>(grid as usize)?);
2503 }
2504 let total_rows_device = self.memory().alloc::<u32>(1)?;
2505
2506 let runtime = self.memory().runtime().ok_or_else(|| {
2507 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
2508 })?;
2509 let cu_stream = runtime
2510 .stream_pool()
2511 .resolve(launch_stream)
2512 .ok_or_else(|| {
2513 XlogError::Kernel(format!(
2514 "{ctx}: launch_stream StreamId({}) does not resolve",
2515 launch_stream.0
2516 ))
2517 })?;
2518
2519 let xy_col0 = &metadata_column_u64(e_xy, 0)?;
2520 let xy_col1 = &metadata_column_u64(e_xy, 1)?;
2521 let yz_col1 = &metadata_column_u64(e_yz, 1)?;
2522 let xz_col1 = &metadata_column_u64(e_xz, 1)?;
2523 let n_yz = self.metadata_logical_rows(e_yz)?;
2524 let n_xz = self.metadata_logical_rows(e_xz)?;
2525
2526 let count_u32 = if grid <= 1024 {
2527 &plan.block_counts
2528 } else {
2529 local_counts
2530 .as_ref()
2531 .expect("local HG counts allocated when grid exceeds single-block scan")
2532 };
2533 let mut scan_scratch =
2534 self.multiblock_scan_u32_scratch_for_len(if grid > 1024 { grid } else { 0 })?;
2535 let mut rec_hg = LaunchRecorder::new_strict(launch_stream);
2536 rec_hg.read(e_xy.num_rows_device());
2537 rec_hg.read(e_yz.num_rows_device());
2538 rec_hg.read(e_xz.num_rows_device());
2539 rec_hg.read_column(e_yz.column(1).expect("yz.col1"));
2540 rec_hg.read_column(e_xz.column(1).expect("xz.col1"));
2541 rec_hg.read(&plan.xy_work_prefix);
2542 rec_hg.read(&plan.xy_yz_start);
2543 rec_hg.read(&plan.xy_yz_end);
2544 rec_hg.read(&plan.xy_xz_start);
2545 rec_hg.read(&plan.xy_xz_end);
2546 rec_hg.read_write(count_u32);
2547 if grid <= 1024 {
2548 rec_hg.read_write(&plan.block_offsets);
2549 } else {
2550 rec_hg.read_write(
2551 local_offsets
2552 .as_ref()
2553 .expect("local HG offsets allocated when grid exceeds single-block scan"),
2554 );
2555 }
2556 rec_hg.write(&total_rows_device);
2557 for level in scan_scratch.levels() {
2558 rec_hg.read_write(level);
2559 }
2560 rec_hg
2561 .preflight(runtime)
2562 .map_err(|e| XlogError::Kernel(format!("{ctx}: HG preflight failed: {e}")))?;
2563 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
2564 {
2565 let kernel = self
2566 .device()
2567 .inner()
2568 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_TRIANGLE_COUNT_HG_U64)
2569 .ok_or_else(|| {
2570 XlogError::Kernel("wcoj_triangle_count_hg_u64 kernel not found".to_string())
2571 })?;
2572 let mut params: Vec<*mut c_void> = vec![
2573 yz_col1.as_kernel_param(),
2574 n_yz.as_kernel_param(),
2575 xz_col1.as_kernel_param(),
2576 n_xz.as_kernel_param(),
2577 (&plan.xy_work_prefix).as_kernel_param(),
2578 (&plan.xy_yz_start).as_kernel_param(),
2579 (&plan.xy_yz_end).as_kernel_param(),
2580 (&plan.xy_xz_start).as_kernel_param(),
2581 (&plan.xy_xz_end).as_kernel_param(),
2582 plan.row_count.as_kernel_param(),
2583 plan.total_work.as_kernel_param(),
2584 plan.block_work_unit.as_kernel_param(),
2585 count_u32.as_kernel_param(),
2586 ];
2587 unsafe {
2588 kernel
2589 .clone()
2590 .launch_in(
2591 stream,
2592 LaunchConfig {
2593 grid_dim: (grid, 1, 1),
2594 block_dim: (BLOCK_SIZE, 1, 1),
2595 shared_mem_bytes: 0,
2596 },
2597 &mut params,
2598 )
2599 .map_err(|e| {
2600 XlogError::Kernel(format!("{ctx}: count launch failed: {e}"))
2601 })?;
2602 }
2603 }
2604 if grid <= 1024 {
2605 let kernel = self
2606 .device()
2607 .inner()
2608 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_SCAN_HG_BLOCK_COUNTS_U32)
2609 .ok_or_else(|| {
2610 XlogError::Kernel(
2611 "wcoj_scan_hg_block_counts_u32 kernel not found".to_string(),
2612 )
2613 })?;
2614 let mut params: Vec<*mut c_void> = vec![
2615 count_u32.as_kernel_param(),
2616 grid.as_kernel_param(),
2617 (&plan.block_offsets).as_kernel_param(),
2618 (&total_rows_device).as_kernel_param(),
2619 ];
2620 unsafe {
2621 kernel
2622 .clone()
2623 .launch_in(
2624 stream,
2625 LaunchConfig {
2626 grid_dim: (1, 1, 1),
2627 block_dim: (1024, 1, 1),
2628 shared_mem_bytes: 0,
2629 },
2630 &mut params,
2631 )
2632 .map_err(|e| {
2633 XlogError::Kernel(format!("{ctx}: HG block-count scan failed: {e}"))
2634 })?;
2635 }
2636 } else {
2637 let offsets_mut = local_offsets
2638 .as_mut()
2639 .expect("local HG offsets allocated when grid exceeds single-block scan");
2640 unsafe {
2641 let res = sys::cuMemcpyDtoDAsync_v2(
2642 *offsets_mut.device_ptr(),
2643 *count_u32.device_ptr(),
2644 bytes_count,
2645 stream.stream().cu_stream(),
2646 );
2647 if res != sys::cudaError_enum::CUDA_SUCCESS {
2648 return Err(XlogError::Kernel(format!(
2649 "{ctx}: DtoD count to offsets failed: {res:?}"
2650 )));
2651 }
2652 }
2653 self.multiblock_scan_u32_inplace_on_stream(
2654 offsets_mut,
2655 grid,
2656 stream,
2657 &mut scan_scratch,
2658 )?;
2659 let total_kernel = self
2660 .device()
2661 .inner()
2662 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_COMPUTE_TOTAL)
2663 .ok_or_else(|| {
2664 XlogError::Kernel("wcoj_compute_total kernel not found".to_string())
2665 })?;
2666 let mut params: Vec<*mut c_void> = vec![
2667 count_u32.as_kernel_param(),
2668 (&*offsets_mut).as_kernel_param(),
2669 grid.as_kernel_param(),
2670 (&total_rows_device).as_kernel_param(),
2671 ];
2672 unsafe {
2673 total_kernel
2674 .clone()
2675 .launch_in(
2676 stream,
2677 LaunchConfig {
2678 grid_dim: (1, 1, 1),
2679 block_dim: (1, 1, 1),
2680 shared_mem_bytes: 0,
2681 },
2682 &mut params,
2683 )
2684 .map_err(|e| {
2685 XlogError::Kernel(format!("{ctx}: HG total reducer failed: {e}"))
2686 })?;
2687 }
2688 }
2689 Ok(())
2690 };
2691 let rec_hg = unsafe { rec_hg.enqueue_prepared_with(&cu_stream, enqueue) }
2693 .map_err(|error| error.into_xlog_error())?;
2694
2695 rec_hg
2696 .commit()
2697 .map_err(|e| XlogError::Kernel(format!("{ctx}: HG count commit failed: {e}")))?;
2698 cu_stream
2699 .synchronize()
2700 .map_err(|e| XlogError::Kernel(format!("{ctx}: count stream sync failed: {e}")))?;
2701 let total_rows = self
2702 .dtoh_scalar_untracked::<u32>(&total_rows_device, 0)
2703 .map_err(|e| XlogError::Kernel(format!("{ctx}: read total rows failed: {e}")))?;
2704 if total_rows == 0 {
2705 return self.create_empty_buffer(out_schema);
2706 }
2707
2708 let bytes_per_col = (total_rows as usize)
2709 .checked_mul(std::mem::size_of::<u64>())
2710 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: output byte size overflow")))?;
2711 let mut out_x = self.memory().alloc::<u8>(bytes_per_col)?;
2712 let mut out_y = self.memory().alloc::<u8>(bytes_per_col)?;
2713 let mut out_z = self.memory().alloc::<u8>(bytes_per_col)?;
2714 let materialize_offsets = if grid <= 1024 {
2715 &plan.block_offsets
2716 } else {
2717 local_offsets
2718 .as_ref()
2719 .expect("local HG offsets allocated when grid exceeds single-block scan")
2720 };
2721 let mut rec_mat = LaunchRecorder::new_strict(launch_stream);
2722 rec_mat.read(materialize_offsets);
2723 rec_mat.read(e_xy.num_rows_device());
2724 rec_mat.read(e_yz.num_rows_device());
2725 rec_mat.read(e_xz.num_rows_device());
2726 rec_mat.read_column(e_xy.column(0).expect("xy.col0"));
2727 rec_mat.read_column(e_xy.column(1).expect("xy.col1"));
2728 rec_mat.read_column(e_yz.column(1).expect("yz.col1"));
2729 rec_mat.read_column(e_xz.column(1).expect("xz.col1"));
2730 rec_mat.read(&plan.xy_work_prefix);
2731 rec_mat.read(&plan.xy_yz_start);
2732 rec_mat.read(&plan.xy_yz_end);
2733 rec_mat.read(&plan.xy_xz_start);
2734 rec_mat.read(&plan.xy_xz_end);
2735 rec_mat.write(&out_x);
2736 rec_mat.write(&out_y);
2737 rec_mat.write(&out_z);
2738 rec_mat
2739 .preflight(runtime)
2740 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize preflight failed: {e}")))?;
2741 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
2742 {
2743 let kernel = self
2744 .device()
2745 .inner()
2746 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_TRIANGLE_MATERIALIZE_HG_U64)
2747 .ok_or_else(|| {
2748 XlogError::Kernel(
2749 "wcoj_triangle_materialize_hg_u64 kernel not found".to_string(),
2750 )
2751 })?;
2752 let out_x_u64 = &mut reinterpret_u8_as_u64(&mut out_x)?;
2753 let out_y_u64 = &mut reinterpret_u8_as_u64(&mut out_y)?;
2754 let out_z_u64 = &mut reinterpret_u8_as_u64(&mut out_z)?;
2755 let mut params: Vec<*mut c_void> = vec![
2756 xy_col0.as_kernel_param(),
2757 xy_col1.as_kernel_param(),
2758 yz_col1.as_kernel_param(),
2759 n_yz.as_kernel_param(),
2760 xz_col1.as_kernel_param(),
2761 n_xz.as_kernel_param(),
2762 (&plan.xy_work_prefix).as_kernel_param(),
2763 (&plan.xy_yz_start).as_kernel_param(),
2764 (&plan.xy_yz_end).as_kernel_param(),
2765 (&plan.xy_xz_start).as_kernel_param(),
2766 (&plan.xy_xz_end).as_kernel_param(),
2767 plan.row_count.as_kernel_param(),
2768 plan.total_work.as_kernel_param(),
2769 plan.block_work_unit.as_kernel_param(),
2770 materialize_offsets.as_kernel_param(),
2771 total_rows.as_kernel_param(),
2772 out_x_u64.as_kernel_param(),
2773 out_y_u64.as_kernel_param(),
2774 out_z_u64.as_kernel_param(),
2775 ];
2776 unsafe {
2777 kernel
2778 .clone()
2779 .launch_in(
2780 stream,
2781 LaunchConfig {
2782 grid_dim: (grid, 1, 1),
2783 block_dim: (BLOCK_SIZE, 1, 1),
2784 shared_mem_bytes: 0,
2785 },
2786 &mut params,
2787 )
2788 .map_err(|e| {
2789 XlogError::Kernel(format!("{ctx}: materialize launch failed: {e}"))
2790 })?;
2791 }
2792 }
2793 Ok(())
2794 };
2795 let rec_mat = unsafe { rec_mat.enqueue_prepared_with(&cu_stream, enqueue) }
2797 .map_err(|error| error.into_xlog_error())?;
2798
2799 rec_mat
2800 .commit()
2801 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize commit failed: {e}")))?;
2802 cu_stream.synchronize().map_err(|e| {
2803 XlogError::Kernel(format!("{ctx}: materialize stream sync failed: {e}"))
2804 })?;
2805
2806 Ok(CudaBuffer::from_columns_with_host_count(
2807 vec![out_x.into(), out_y.into(), out_z.into()],
2808 total_rows as u64,
2809 total_rows_device,
2810 out_schema,
2811 total_rows,
2812 ))
2813 }
2814
2815 pub fn wcoj_4cycle_hg_work_plan_u32_recorded(
2816 &self,
2817 e1: &CudaBuffer,
2818 e2: &CudaBuffer,
2819 e3: &CudaBuffer,
2820 e4: &CudaBuffer,
2821 block_work_unit: u32,
2822 launch_stream: StreamId,
2823 ) -> Result<WcojCycle4HgWorkPlanU32> {
2824 let ctx = "wcoj_4cycle_hg_work_plan_u32_recorded";
2825 if block_work_unit == 0 {
2826 return Err(XlogError::Kernel(format!(
2827 "{ctx}: block_work_unit must be nonzero"
2828 )));
2829 }
2830 validate_binary_u32(ctx, "e1", e1)?;
2831 validate_binary_u32(ctx, "e2", e2)?;
2832 validate_binary_u32(ctx, "e3", e3)?;
2833 validate_binary_u32(ctx, "e4", e4)?;
2834
2835 let n_e1 = self.metadata_logical_rows(e1)?;
2836 let n_e2 = self.metadata_logical_rows(e2)?;
2837 let n_e3 = self.metadata_logical_rows(e3)?;
2838 let prefix_len = n_e1
2839 .checked_add(1)
2840 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: prefix length overflow")))?;
2841 let e2_prefix_len = n_e2
2842 .checked_add(1)
2843 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: e2 prefix length overflow")))?;
2844 let mut e1_work_prefix = self.memory().alloc::<u32>(prefix_len as usize)?;
2845 let mut e2_work_prefix = self.memory().alloc::<u32>(e2_prefix_len as usize)?;
2846 let mut e1_e2_start = self.memory().alloc::<u32>(n_e1 as usize)?;
2847 let mut e1_e2_end = self.memory().alloc::<u32>(n_e1 as usize)?;
2848
2849 if n_e1 == 0 || n_e2 == 0 || n_e3 == 0 || self.metadata_logical_rows(e4)? == 0 {
2850 let block_counts = self.memory().alloc::<u32>(1)?;
2851 let block_offsets = self.memory().alloc::<u32>(1)?;
2852 return Ok(WcojCycle4HgWorkPlanU32 {
2853 e1_work_prefix,
2854 e2_work_prefix,
2855 e1_e2_start,
2856 e1_e2_end,
2857 block_counts,
2858 block_offsets,
2859 total_work: 0,
2860 block_work_unit,
2861 row_count: n_e1,
2862 });
2863 }
2864
2865 let runtime = self.memory().runtime().ok_or_else(|| {
2866 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
2867 })?;
2868 let cu_stream = runtime
2869 .stream_pool()
2870 .resolve(launch_stream)
2871 .ok_or_else(|| {
2872 XlogError::Kernel(format!(
2873 "{ctx}: launch_stream StreamId({}) does not resolve",
2874 launch_stream.0
2875 ))
2876 })?;
2877
2878 let e1_col1 = &metadata_column_u32(e1, 1)?;
2879 let e2_col0 = &metadata_column_u32(e2, 0)?;
2880 let e2_col1 = &metadata_column_u32(e2, 1)?;
2881 let e3_col0 = &metadata_column_u32(e3, 0)?;
2882
2883 let mut scan_scratch =
2884 self.multiblock_scan_u32_scratch_for_len(e2_prefix_len.max(prefix_len))?;
2885 let mut rec = LaunchRecorder::new_strict(launch_stream);
2886 rec.read(e1.num_rows_device());
2887 rec.read(e2.num_rows_device());
2888 rec.read(e3.num_rows_device());
2889 rec.read_column(e1.column(1).expect("e1.col1"));
2890 rec.read_column(e2.column(0).expect("e2.col0"));
2891 rec.read_column(e2.column(1).expect("e2.col1"));
2892 rec.read_column(e3.column(0).expect("e3.col0"));
2893 rec.read_write(&e2_work_prefix);
2894 rec.write(&e1_work_prefix);
2895 rec.write(&e1_e2_start);
2896 rec.write(&e1_e2_end);
2897 for level in scan_scratch.levels() {
2898 rec.read_write(level);
2899 }
2900 rec.preflight(runtime)
2901 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
2902 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
2903 let e2_kernel = self
2904 .device()
2905 .inner()
2906 .get_func(
2907 WCOJ_MODULE,
2908 wcoj_kernels::WCOJ_4CYCLE_BUILD_E2_WORK_PREFIX_U32,
2909 )
2910 .ok_or_else(|| {
2911 XlogError::Kernel(
2912 "wcoj_4cycle_build_e2_work_prefix_u32 kernel not found".to_string(),
2913 )
2914 })?;
2915 let e2_grid = n_e2.div_ceil(BLOCK_SIZE);
2916 unsafe {
2917 e2_kernel
2918 .clone()
2919 .launch_in(
2920 stream,
2921 LaunchConfig {
2922 grid_dim: (e2_grid, 1, 1),
2923 block_dim: (BLOCK_SIZE, 1, 1),
2924 shared_mem_bytes: 0,
2925 },
2926 (e2_col1, n_e2, e3_col0, n_e3, &mut e2_work_prefix),
2927 )
2928 .map_err(|e| {
2929 XlogError::Kernel(format!(
2930 "wcoj_4cycle_build_e2_work_prefix_u32 launch failed: {e}"
2931 ))
2932 })?;
2933 }
2934 self.multiblock_scan_u32_inplace_on_stream(
2935 &mut e2_work_prefix,
2936 e2_prefix_len,
2937 stream,
2938 &mut scan_scratch,
2939 )?;
2940
2941 let kernel = self
2942 .device()
2943 .inner()
2944 .get_func(
2945 WCOJ_MODULE,
2946 wcoj_kernels::WCOJ_4CYCLE_BUILD_HG_WORK_PLAN_U32,
2947 )
2948 .ok_or_else(|| {
2949 XlogError::Kernel(
2950 "wcoj_4cycle_build_hg_work_plan_u32 kernel not found".to_string(),
2951 )
2952 })?;
2953 let grid = n_e1.div_ceil(BLOCK_SIZE);
2954 unsafe {
2955 kernel
2956 .clone()
2957 .launch_in(
2958 stream,
2959 LaunchConfig {
2960 grid_dim: (grid, 1, 1),
2961 block_dim: (BLOCK_SIZE, 1, 1),
2962 shared_mem_bytes: 0,
2963 },
2964 (
2965 e1_col1,
2966 n_e1,
2967 e2_col0,
2968 n_e2,
2969 &e2_work_prefix,
2970 &mut e1_work_prefix,
2971 &mut e1_e2_start,
2972 &mut e1_e2_end,
2973 ),
2974 )
2975 .map_err(|e| {
2976 XlogError::Kernel(format!(
2977 "wcoj_4cycle_build_hg_work_plan_u32 launch failed: {e}"
2978 ))
2979 })?;
2980 }
2981 self.multiblock_scan_u32_inplace_on_stream(
2982 &mut e1_work_prefix,
2983 prefix_len,
2984 stream,
2985 &mut scan_scratch,
2986 )?;
2987 Ok(())
2988 };
2989 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
2991 .map_err(|error| error.into_xlog_error())?;
2992
2993 rec.commit()
2994 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
2995 cu_stream
2996 .synchronize()
2997 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
2998 let total_work = self.dtoh_scalar_untracked::<u32>(&e1_work_prefix, n_e1 as usize)?;
2999 let grid = if total_work == 0 {
3000 1
3001 } else {
3002 total_work.div_ceil(block_work_unit)
3003 };
3004 let block_counts = self.memory().alloc::<u32>(grid as usize)?;
3005 let block_offsets = self.memory().alloc::<u32>(grid as usize)?;
3006
3007 Ok(WcojCycle4HgWorkPlanU32 {
3008 e1_work_prefix,
3009 e2_work_prefix,
3010 e1_e2_start,
3011 e1_e2_end,
3012 block_counts,
3013 block_offsets,
3014 total_work,
3015 block_work_unit,
3016 row_count: n_e1,
3017 })
3018 }
3019
3020 pub fn wcoj_4cycle_hg_u32_recorded(
3021 &self,
3022 e1: &CudaBuffer,
3023 e2: &CudaBuffer,
3024 e3: &CudaBuffer,
3025 e4: &CudaBuffer,
3026 block_work_unit: u32,
3027 launch_stream: StreamId,
3028 ) -> Result<CudaBuffer> {
3029 let ctx = "wcoj_4cycle_hg_u32_recorded";
3030 validate_binary_u32(ctx, "e1", e1)?;
3031 validate_binary_u32(ctx, "e2", e2)?;
3032 validate_binary_u32(ctx, "e3", e3)?;
3033 validate_binary_u32(ctx, "e4", e4)?;
3034 let plan = self.wcoj_4cycle_hg_work_plan_u32_recorded(
3035 e1,
3036 e2,
3037 e3,
3038 e4,
3039 block_work_unit,
3040 launch_stream,
3041 )?;
3042 let out_schema = Schema::new(vec![
3043 (
3044 "col0".to_string(),
3045 e1.schema().column_type(0).expect("e1.col0 type"),
3046 ),
3047 (
3048 "col1".to_string(),
3049 e1.schema().column_type(1).expect("e1.col1 type"),
3050 ),
3051 (
3052 "col2".to_string(),
3053 e2.schema().column_type(1).expect("e2.col1 type"),
3054 ),
3055 (
3056 "col3".to_string(),
3057 e3.schema().column_type(1).expect("e3.col1 type"),
3058 ),
3059 ]);
3060 if plan.total_work == 0 {
3061 return self.create_empty_buffer(out_schema);
3062 }
3063
3064 let grid = plan.total_work.div_ceil(plan.block_work_unit);
3065 let bytes_count = (grid as usize)
3066 .checked_mul(std::mem::size_of::<u32>())
3067 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: count byte size overflow")))?;
3068 let mut local_counts = None;
3069 let mut local_offsets = None;
3070 if grid > 1024 {
3071 local_counts = Some(self.memory().alloc::<u32>(grid as usize)?);
3072 local_offsets = Some(self.memory().alloc::<u32>(grid as usize)?);
3073 }
3074 let total_rows_device = self.memory().alloc::<u32>(1)?;
3075
3076 let runtime = self.memory().runtime().ok_or_else(|| {
3077 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
3078 })?;
3079 let cu_stream = runtime
3080 .stream_pool()
3081 .resolve(launch_stream)
3082 .ok_or_else(|| {
3083 XlogError::Kernel(format!(
3084 "{ctx}: launch_stream StreamId({}) does not resolve",
3085 launch_stream.0
3086 ))
3087 })?;
3088
3089 let e1_col0 = &metadata_column_u32(e1, 0)?;
3090 let e1_col1 = &metadata_column_u32(e1, 1)?;
3091 let e2_col1 = &metadata_column_u32(e2, 1)?;
3092 let e3_col0 = &metadata_column_u32(e3, 0)?;
3093 let e3_col1 = &metadata_column_u32(e3, 1)?;
3094 let e4_col0 = &metadata_column_u32(e4, 0)?;
3095 let e4_col1 = &metadata_column_u32(e4, 1)?;
3096 let n_e3 = self.metadata_logical_rows(e3)?;
3097 let n_e4 = self.metadata_logical_rows(e4)?;
3098
3099 let count_u32 = if grid <= 1024 {
3100 &plan.block_counts
3101 } else {
3102 local_counts
3103 .as_ref()
3104 .expect("local HG counts allocated when grid exceeds single-block scan")
3105 };
3106 let mut scan_scratch =
3107 self.multiblock_scan_u32_scratch_for_len(if grid > 1024 { grid } else { 0 })?;
3108 let mut rec_hg = LaunchRecorder::new_strict(launch_stream);
3109 rec_hg.read(e1.num_rows_device());
3110 rec_hg.read(e2.num_rows_device());
3111 rec_hg.read(e3.num_rows_device());
3112 rec_hg.read(e4.num_rows_device());
3113 rec_hg.read_column(e1.column(0).expect("e1.col0"));
3114 rec_hg.read_column(e1.column(1).expect("e1.col1"));
3115 rec_hg.read_column(e2.column(1).expect("e2.col1"));
3116 rec_hg.read_column(e3.column(0).expect("e3.col0"));
3117 rec_hg.read_column(e3.column(1).expect("e3.col1"));
3118 rec_hg.read_column(e4.column(0).expect("e4.col0"));
3119 rec_hg.read_column(e4.column(1).expect("e4.col1"));
3120 rec_hg.read(&plan.e1_work_prefix);
3121 rec_hg.read(&plan.e2_work_prefix);
3122 rec_hg.read(&plan.e1_e2_start);
3123 rec_hg.read(&plan.e1_e2_end);
3124 rec_hg.read_write(count_u32);
3125 if grid <= 1024 {
3126 rec_hg.read_write(&plan.block_offsets);
3127 } else {
3128 rec_hg.read_write(
3129 local_offsets
3130 .as_ref()
3131 .expect("local HG offsets allocated when grid exceeds single-block scan"),
3132 );
3133 }
3134 rec_hg.write(&total_rows_device);
3135 for level in scan_scratch.levels() {
3136 rec_hg.read_write(level);
3137 }
3138 rec_hg
3139 .preflight(runtime)
3140 .map_err(|e| XlogError::Kernel(format!("{ctx}: HG preflight failed: {e}")))?;
3141 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
3142 {
3143 let kernel = self
3144 .device()
3145 .inner()
3146 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_4CYCLE_COUNT_HG_U32)
3147 .ok_or_else(|| {
3148 XlogError::Kernel("wcoj_4cycle_count_hg_u32 kernel not found".to_string())
3149 })?;
3150 let mut params: Vec<*mut c_void> = vec![
3151 e1_col0.as_kernel_param(),
3152 e1_col1.as_kernel_param(),
3153 plan.row_count.as_kernel_param(),
3154 e2_col1.as_kernel_param(),
3155 e3_col0.as_kernel_param(),
3156 e3_col1.as_kernel_param(),
3157 n_e3.as_kernel_param(),
3158 e4_col0.as_kernel_param(),
3159 e4_col1.as_kernel_param(),
3160 n_e4.as_kernel_param(),
3161 (&plan.e1_work_prefix).as_kernel_param(),
3162 (&plan.e2_work_prefix).as_kernel_param(),
3163 (&plan.e1_e2_start).as_kernel_param(),
3164 (&plan.e1_e2_end).as_kernel_param(),
3165 plan.total_work.as_kernel_param(),
3166 plan.block_work_unit.as_kernel_param(),
3167 count_u32.as_kernel_param(),
3168 ];
3169 unsafe {
3170 kernel
3171 .clone()
3172 .launch_in(
3173 stream,
3174 LaunchConfig {
3175 grid_dim: (grid, 1, 1),
3176 block_dim: (BLOCK_SIZE, 1, 1),
3177 shared_mem_bytes: 0,
3178 },
3179 &mut params,
3180 )
3181 .map_err(|e| {
3182 XlogError::Kernel(format!("{ctx}: count launch failed: {e}"))
3183 })?;
3184 }
3185 }
3186 if grid <= 1024 {
3187 let kernel = self
3188 .device()
3189 .inner()
3190 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_SCAN_HG_BLOCK_COUNTS_U32)
3191 .ok_or_else(|| {
3192 XlogError::Kernel(
3193 "wcoj_scan_hg_block_counts_u32 kernel not found".to_string(),
3194 )
3195 })?;
3196 let mut params: Vec<*mut c_void> = vec![
3197 count_u32.as_kernel_param(),
3198 grid.as_kernel_param(),
3199 (&plan.block_offsets).as_kernel_param(),
3200 (&total_rows_device).as_kernel_param(),
3201 ];
3202 unsafe {
3203 kernel
3204 .clone()
3205 .launch_in(
3206 stream,
3207 LaunchConfig {
3208 grid_dim: (1, 1, 1),
3209 block_dim: (1024, 1, 1),
3210 shared_mem_bytes: 0,
3211 },
3212 &mut params,
3213 )
3214 .map_err(|e| XlogError::Kernel(format!("{ctx}: scan failed: {e}")))?;
3215 }
3216 } else {
3217 let offsets_mut = local_offsets
3218 .as_mut()
3219 .expect("local HG offsets allocated when grid exceeds single-block scan");
3220 unsafe {
3221 let res = sys::cuMemcpyDtoDAsync_v2(
3222 *offsets_mut.device_ptr(),
3223 *count_u32.device_ptr(),
3224 bytes_count,
3225 stream.stream().cu_stream(),
3226 );
3227 if res != sys::cudaError_enum::CUDA_SUCCESS {
3228 return Err(XlogError::Kernel(format!(
3229 "{ctx}: DtoD count to offsets failed: {res:?}"
3230 )));
3231 }
3232 }
3233 self.multiblock_scan_u32_inplace_on_stream(
3234 offsets_mut,
3235 grid,
3236 stream,
3237 &mut scan_scratch,
3238 )?;
3239 let total_kernel = self
3240 .device()
3241 .inner()
3242 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_COMPUTE_TOTAL)
3243 .ok_or_else(|| {
3244 XlogError::Kernel("wcoj_compute_total kernel not found".to_string())
3245 })?;
3246 let mut params: Vec<*mut c_void> = vec![
3247 count_u32.as_kernel_param(),
3248 (&*offsets_mut).as_kernel_param(),
3249 grid.as_kernel_param(),
3250 (&total_rows_device).as_kernel_param(),
3251 ];
3252 unsafe {
3253 total_kernel
3254 .clone()
3255 .launch_in(
3256 stream,
3257 LaunchConfig {
3258 grid_dim: (1, 1, 1),
3259 block_dim: (1, 1, 1),
3260 shared_mem_bytes: 0,
3261 },
3262 &mut params,
3263 )
3264 .map_err(|e| XlogError::Kernel(format!("{ctx}: total failed: {e}")))?;
3265 }
3266 }
3267 Ok(())
3268 };
3269 let rec_hg = unsafe { rec_hg.enqueue_prepared_with(&cu_stream, enqueue) }
3271 .map_err(|error| error.into_xlog_error())?;
3272
3273 rec_hg
3274 .commit()
3275 .map_err(|e| XlogError::Kernel(format!("{ctx}: count commit failed: {e}")))?;
3276 cu_stream
3277 .synchronize()
3278 .map_err(|e| XlogError::Kernel(format!("{ctx}: count stream sync failed: {e}")))?;
3279 let total_rows = self
3280 .dtoh_scalar_untracked::<u32>(&total_rows_device, 0)
3281 .map_err(|e| XlogError::Kernel(format!("{ctx}: read total rows failed: {e}")))?;
3282 if total_rows == 0 {
3283 return self.create_empty_buffer(out_schema);
3284 }
3285
3286 let bytes_per_col = (total_rows as usize)
3287 .checked_mul(std::mem::size_of::<u32>())
3288 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: output byte size overflow")))?;
3289 let mut out_w = self.memory().alloc::<u8>(bytes_per_col)?;
3290 let mut out_x = self.memory().alloc::<u8>(bytes_per_col)?;
3291 let mut out_y = self.memory().alloc::<u8>(bytes_per_col)?;
3292 let mut out_z = self.memory().alloc::<u8>(bytes_per_col)?;
3293 let materialize_offsets = if grid <= 1024 {
3294 &plan.block_offsets
3295 } else {
3296 local_offsets
3297 .as_ref()
3298 .expect("local HG offsets allocated when grid exceeds single-block scan")
3299 };
3300 let mut rec_mat = LaunchRecorder::new_strict(launch_stream);
3301 rec_mat.read(materialize_offsets);
3302 rec_mat.read(e1.num_rows_device());
3303 rec_mat.read(e2.num_rows_device());
3304 rec_mat.read(e3.num_rows_device());
3305 rec_mat.read(e4.num_rows_device());
3306 rec_mat.read_column(e1.column(0).expect("e1.col0"));
3307 rec_mat.read_column(e1.column(1).expect("e1.col1"));
3308 rec_mat.read_column(e2.column(1).expect("e2.col1"));
3309 rec_mat.read_column(e3.column(0).expect("e3.col0"));
3310 rec_mat.read_column(e3.column(1).expect("e3.col1"));
3311 rec_mat.read_column(e4.column(0).expect("e4.col0"));
3312 rec_mat.read_column(e4.column(1).expect("e4.col1"));
3313 rec_mat.read(&plan.e1_work_prefix);
3314 rec_mat.read(&plan.e2_work_prefix);
3315 rec_mat.read(&plan.e1_e2_start);
3316 rec_mat.read(&plan.e1_e2_end);
3317 rec_mat.write(&out_w);
3318 rec_mat.write(&out_x);
3319 rec_mat.write(&out_y);
3320 rec_mat.write(&out_z);
3321 rec_mat
3322 .preflight(runtime)
3323 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize preflight failed: {e}")))?;
3324 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
3325 {
3326 let kernel = self
3327 .device()
3328 .inner()
3329 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_4CYCLE_MATERIALIZE_HG_U32)
3330 .ok_or_else(|| {
3331 XlogError::Kernel(
3332 "wcoj_4cycle_materialize_hg_u32 kernel not found".to_string(),
3333 )
3334 })?;
3335 let out_w_u32 = &mut reinterpret_u8_as_u32(&mut out_w)?;
3336 let out_x_u32 = &mut reinterpret_u8_as_u32(&mut out_x)?;
3337 let out_y_u32 = &mut reinterpret_u8_as_u32(&mut out_y)?;
3338 let out_z_u32 = &mut reinterpret_u8_as_u32(&mut out_z)?;
3339 let mut params: Vec<*mut c_void> = vec![
3340 e1_col0.as_kernel_param(),
3341 e1_col1.as_kernel_param(),
3342 plan.row_count.as_kernel_param(),
3343 e2_col1.as_kernel_param(),
3344 e3_col0.as_kernel_param(),
3345 e3_col1.as_kernel_param(),
3346 n_e3.as_kernel_param(),
3347 e4_col0.as_kernel_param(),
3348 e4_col1.as_kernel_param(),
3349 n_e4.as_kernel_param(),
3350 (&plan.e1_work_prefix).as_kernel_param(),
3351 (&plan.e2_work_prefix).as_kernel_param(),
3352 (&plan.e1_e2_start).as_kernel_param(),
3353 (&plan.e1_e2_end).as_kernel_param(),
3354 plan.total_work.as_kernel_param(),
3355 plan.block_work_unit.as_kernel_param(),
3356 materialize_offsets.as_kernel_param(),
3357 total_rows.as_kernel_param(),
3358 out_w_u32.as_kernel_param(),
3359 out_x_u32.as_kernel_param(),
3360 out_y_u32.as_kernel_param(),
3361 out_z_u32.as_kernel_param(),
3362 ];
3363 unsafe {
3364 kernel
3365 .clone()
3366 .launch_in(
3367 stream,
3368 LaunchConfig {
3369 grid_dim: (grid, 1, 1),
3370 block_dim: (BLOCK_SIZE, 1, 1),
3371 shared_mem_bytes: 0,
3372 },
3373 &mut params,
3374 )
3375 .map_err(|e| {
3376 XlogError::Kernel(format!("{ctx}: materialize launch failed: {e}"))
3377 })?;
3378 }
3379 }
3380 Ok(())
3381 };
3382 let rec_mat = unsafe { rec_mat.enqueue_prepared_with(&cu_stream, enqueue) }
3384 .map_err(|error| error.into_xlog_error())?;
3385
3386 rec_mat
3387 .commit()
3388 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize commit failed: {e}")))?;
3389 cu_stream.synchronize().map_err(|e| {
3390 XlogError::Kernel(format!("{ctx}: materialize stream sync failed: {e}"))
3391 })?;
3392
3393 Ok(CudaBuffer::from_columns_with_host_count(
3394 vec![out_w.into(), out_x.into(), out_y.into(), out_z.into()],
3395 total_rows as u64,
3396 total_rows_device,
3397 out_schema,
3398 total_rows,
3399 ))
3400 }
3401
3402 pub fn wcoj_4cycle_groupby_root_count_u32_recorded(
3425 &self,
3426 e1: &CudaBuffer,
3427 e2: &CudaBuffer,
3428 e3: &CudaBuffer,
3429 e4: &CudaBuffer,
3430 block_work_unit: u32,
3431 launch_stream: StreamId,
3432 ) -> Result<CudaBuffer> {
3433 let ctx = "wcoj_4cycle_groupby_root_count_u32_recorded";
3434 let e1 = &self.wcoj_layout_u32_recorded(e1, launch_stream)?;
3440 let e2 = &self.wcoj_layout_u32_recorded(e2, launch_stream)?;
3441 let e3 = &self.wcoj_layout_u32_recorded(e3, launch_stream)?;
3442 let e4 = &self.wcoj_layout_u32_recorded(e4, launch_stream)?;
3443 validate_binary_u32(ctx, "e1", e1)?;
3444 validate_binary_u32(ctx, "e2", e2)?;
3445 validate_binary_u32(ctx, "e3", e3)?;
3446 validate_binary_u32(ctx, "e4", e4)?;
3447 let plan = self.wcoj_4cycle_hg_work_plan_u32_recorded(
3448 e1,
3449 e2,
3450 e3,
3451 e4,
3452 block_work_unit,
3453 launch_stream,
3454 )?;
3455 let n_e1 = plan.row_count;
3456 let w_type = e1.schema().column_type(0).expect("e1.col0 type");
3457 let out_schema = Schema::new(vec![
3458 ("w".to_string(), w_type),
3459 ("count".to_string(), ScalarType::U64),
3460 ]);
3461 if n_e1 == 0 || plan.total_work == 0 {
3462 return self.create_empty_buffer(out_schema);
3463 }
3464
3465 let runtime = self.memory().runtime().ok_or_else(|| {
3466 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
3467 })?;
3468 let cu_stream = runtime
3469 .stream_pool()
3470 .resolve(launch_stream)
3471 .ok_or_else(|| {
3472 XlogError::Kernel(format!(
3473 "{ctx}: launch_stream StreamId({}) does not resolve",
3474 launch_stream.0
3475 ))
3476 })?;
3477
3478 let e1_col0 = &metadata_column_u32(e1, 0)?;
3479 let e1_col1 = &metadata_column_u32(e1, 1)?;
3480 let e2_col1 = &metadata_column_u32(e2, 1)?;
3481 let e3_col0 = &metadata_column_u32(e3, 0)?;
3482 let e3_col1 = &metadata_column_u32(e3, 1)?;
3483 let e4_col0 = &metadata_column_u32(e4, 0)?;
3484 let e4_col1 = &metadata_column_u32(e4, 1)?;
3485 let n_e3 = self.metadata_logical_rows(e3)?;
3486 let n_e4 = self.metadata_logical_rows(e4)?;
3487
3488 let mut row_counts = self
3492 .memory()
3493 .alloc::<u8>(n_e1 as usize * std::mem::size_of::<u32>())?;
3494 self.device()
3495 .inner()
3496 .memset_zeros(&mut row_counts)
3497 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row counts failed: {e}")))?;
3498
3499 let grid = plan.total_work.div_ceil(plan.block_work_unit);
3500 let mut rec = LaunchRecorder::new_strict(launch_stream);
3501 rec.read(e1.num_rows_device());
3502 rec.read(e2.num_rows_device());
3503 rec.read(e3.num_rows_device());
3504 rec.read(e4.num_rows_device());
3505 rec.read_column(e1.column(0).expect("e1.col0"));
3506 rec.read_column(e1.column(1).expect("e1.col1"));
3507 rec.read_column(e2.column(1).expect("e2.col1"));
3508 rec.read_column(e3.column(0).expect("e3.col0"));
3509 rec.read_column(e3.column(1).expect("e3.col1"));
3510 rec.read_column(e4.column(0).expect("e4.col0"));
3511 rec.read_column(e4.column(1).expect("e4.col1"));
3512 rec.read(&plan.e1_work_prefix);
3513 rec.read(&plan.e2_work_prefix);
3514 rec.read(&plan.e1_e2_start);
3515 rec.read(&plan.e1_e2_end);
3516 rec.write(&row_counts);
3517 rec.preflight(runtime)
3518 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
3519 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
3520 {
3521 let kernel = self
3522 .device()
3523 .inner()
3524 .get_func(
3525 WCOJ_MODULE,
3526 wcoj_kernels::WCOJ_4CYCLE_GROUPBY_ROOT_COUNT_HG_U32,
3527 )
3528 .ok_or_else(|| {
3529 XlogError::Kernel(
3530 "wcoj_4cycle_groupby_root_count_hg_u32 kernel not found".to_string(),
3531 )
3532 })?;
3533 let mut params: Vec<*mut c_void> = vec![
3534 e1_col0.as_kernel_param(),
3535 e1_col1.as_kernel_param(),
3536 plan.row_count.as_kernel_param(),
3537 e2_col1.as_kernel_param(),
3538 e3_col0.as_kernel_param(),
3539 e3_col1.as_kernel_param(),
3540 n_e3.as_kernel_param(),
3541 e4_col0.as_kernel_param(),
3542 e4_col1.as_kernel_param(),
3543 n_e4.as_kernel_param(),
3544 (&plan.e1_work_prefix).as_kernel_param(),
3545 (&plan.e2_work_prefix).as_kernel_param(),
3546 (&plan.e1_e2_start).as_kernel_param(),
3547 (&plan.e1_e2_end).as_kernel_param(),
3548 plan.total_work.as_kernel_param(),
3549 plan.block_work_unit.as_kernel_param(),
3550 (&row_counts).as_kernel_param(),
3551 ];
3552 unsafe {
3553 kernel
3554 .clone()
3555 .launch_in(
3556 stream,
3557 LaunchConfig {
3558 grid_dim: (grid, 1, 1),
3559 block_dim: (BLOCK_SIZE, 1, 1),
3560 shared_mem_bytes: 0,
3561 },
3562 &mut params,
3563 )
3564 .map_err(|e| {
3565 XlogError::Kernel(format!("{ctx}: groupby-count launch failed: {e}"))
3566 })?;
3567 }
3568 }
3569 Ok(())
3570 };
3571 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
3573 .map_err(|error| error.into_xlog_error())?;
3574
3575 rec.commit()
3576 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
3577
3578 let w_src = match e1.column(0).expect("e1.col0") {
3582 CudaColumn::Owned(slice) => slice,
3583 _ => {
3584 return Err(XlogError::Kernel(format!(
3585 "{ctx}: e1.col0 must be an owned CudaColumn"
3586 )))
3587 }
3588 };
3589 let mut w_copy = self
3590 .memory()
3591 .alloc::<u8>(n_e1 as usize * std::mem::size_of::<u32>())?;
3592 self.device()
3593 .inner()
3594 .dtod_copy(w_src, &mut w_copy)
3595 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy W column failed: {e}")))?;
3596 let mut d_num_rows = self.memory().alloc::<u32>(1)?;
3597 self.device()
3598 .inner()
3599 .dtod_copy(e1.num_rows_device(), &mut d_num_rows)
3600 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy row count failed: {e}")))?;
3601 let staging_schema = Schema::new(vec![
3602 ("w".to_string(), w_type),
3603 ("count".to_string(), ScalarType::U32),
3604 ]);
3605 let staging = CudaBuffer::from_columns_with_host_count(
3606 vec![w_copy.into(), row_counts.into()],
3607 n_e1 as u64,
3608 d_num_rows,
3609 staging_schema,
3610 n_e1,
3611 );
3612
3613 let mask = self.compare_const_mask_recorded::<u32>(
3616 &staging,
3617 1,
3618 0u32,
3619 crate::CompareOp::Gt,
3620 launch_stream,
3621 )?;
3622 let compacted =
3623 self.compact_buffer_by_device_mask_counted_recorded(&staging, &mask, launch_stream)?;
3624 self.groupby_multi_agg_recorded(
3625 &compacted,
3626 &[0],
3627 &[(1, xlog_core::AggOp::Sum)],
3628 launch_stream,
3629 )
3630 }
3631
3632 #[expect(
3664 clippy::too_many_arguments,
3665 reason = "the recorded four-cycle aggregate mirrors its four inputs, aggregate selector, work unit, and stream"
3666 )]
3667 pub fn wcoj_4cycle_groupby_root_agg_u32_recorded(
3668 &self,
3669 e1: &CudaBuffer,
3670 e2: &CudaBuffer,
3671 e3: &CudaBuffer,
3672 e4: &CudaBuffer,
3673 agg_op: AggOp,
3674 value: Wcoj4CycleRootAggValue,
3675 block_work_unit: u32,
3676 launch_stream: StreamId,
3677 ) -> Result<CudaBuffer> {
3678 let ctx = "wcoj_4cycle_groupby_root_agg_u32_recorded";
3679 let e1 = &self.wcoj_layout_u32_recorded(e1, launch_stream)?;
3685 let e2 = &self.wcoj_layout_u32_recorded(e2, launch_stream)?;
3686 let e3 = &self.wcoj_layout_u32_recorded(e3, launch_stream)?;
3687 let e4 = &self.wcoj_layout_u32_recorded(e4, launch_stream)?;
3688 let (kernel_name, agg_elem_size, agg_scalar, agg_name) = match agg_op {
3689 AggOp::Sum => (
3690 wcoj_kernels::WCOJ_4CYCLE_GROUPBY_ROOT_SUM_HG_U32,
3691 std::mem::size_of::<u64>(),
3692 ScalarType::U64,
3693 "sum_0",
3694 ),
3695 AggOp::Min => (
3696 wcoj_kernels::WCOJ_4CYCLE_GROUPBY_ROOT_MIN_HG_U32,
3697 std::mem::size_of::<u32>(),
3698 ScalarType::U32,
3699 "min_0",
3700 ),
3701 AggOp::Max => (
3702 wcoj_kernels::WCOJ_4CYCLE_GROUPBY_ROOT_MAX_HG_U32,
3703 std::mem::size_of::<u32>(),
3704 ScalarType::U32,
3705 "max_0",
3706 ),
3707 other => {
3708 return Err(XlogError::Kernel(format!(
3709 "{ctx}: unsupported AggOp {other:?} (Sum/Min/Max only; use \
3710 wcoj_4cycle_groupby_root_count_u32_recorded for Count)"
3711 )))
3712 }
3713 };
3714 validate_binary_u32(ctx, "e1", e1)?;
3715 validate_binary_u32(ctx, "e2", e2)?;
3716 validate_binary_u32(ctx, "e3", e3)?;
3717 validate_binary_u32(ctx, "e4", e4)?;
3718 let (value_buf, value_label) = match value {
3724 Wcoj4CycleRootAggValue::X => (e1, "e1"),
3725 Wcoj4CycleRootAggValue::Y => (e2, "e2"),
3726 Wcoj4CycleRootAggValue::Z => (e3, "e3"),
3727 };
3728 {
3729 let ty = value_buf.schema().column_type(1).expect("validated 2-col");
3730 if ty != ScalarType::U32 {
3731 return Err(XlogError::Kernel(format!(
3732 "{ctx}: {value_label}.col1 supplies the aggregate value and must be U32, \
3733 got {ty:?}"
3734 )));
3735 }
3736 }
3737
3738 let plan = self.wcoj_4cycle_hg_work_plan_u32_recorded(
3739 e1,
3740 e2,
3741 e3,
3742 e4,
3743 block_work_unit,
3744 launch_stream,
3745 )?;
3746 let n_e1 = plan.row_count;
3747 let w_type = e1.schema().column_type(0).expect("e1.col0 type");
3748 let out_schema = Schema::new(vec![
3749 ("w".to_string(), w_type),
3750 (agg_name.to_string(), agg_scalar),
3751 ]);
3752 if n_e1 == 0 || plan.total_work == 0 {
3753 return self.create_empty_buffer(out_schema);
3754 }
3755
3756 let runtime = self.memory().runtime().ok_or_else(|| {
3757 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
3758 })?;
3759 let cu_stream = runtime
3760 .stream_pool()
3761 .resolve(launch_stream)
3762 .ok_or_else(|| {
3763 XlogError::Kernel(format!(
3764 "{ctx}: launch_stream StreamId({}) does not resolve",
3765 launch_stream.0
3766 ))
3767 })?;
3768
3769 let e1_col0 = &metadata_column_u32(e1, 0)?;
3770 let e1_col1 = &metadata_column_u32(e1, 1)?;
3771 let e2_col1 = &metadata_column_u32(e2, 1)?;
3772 let e3_col0 = &metadata_column_u32(e3, 0)?;
3773 let e3_col1 = &metadata_column_u32(e3, 1)?;
3774 let e4_col0 = &metadata_column_u32(e4, 0)?;
3775 let e4_col1 = &metadata_column_u32(e4, 1)?;
3776 let n_e3 = self.metadata_logical_rows(e3)?;
3777 let n_e4 = self.metadata_logical_rows(e4)?;
3778 let value_sel: u32 = match value {
3779 Wcoj4CycleRootAggValue::X => 0,
3780 Wcoj4CycleRootAggValue::Y => 1,
3781 Wcoj4CycleRootAggValue::Z => 2,
3782 };
3783
3784 let mut row_counts = self
3788 .memory()
3789 .alloc::<u8>(n_e1 as usize * std::mem::size_of::<u32>())?;
3790 self.device()
3791 .inner()
3792 .memset_zeros(&mut row_counts)
3793 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row counts failed: {e}")))?;
3794 let mut row_agg = self.memory().alloc::<u8>(n_e1 as usize * agg_elem_size)?;
3795 self.device()
3796 .inner()
3797 .memset_zeros(&mut row_agg)
3798 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row aggregates failed: {e}")))?;
3799
3800 let grid = plan.total_work.div_ceil(plan.block_work_unit);
3801 let mut rec = LaunchRecorder::new_strict(launch_stream);
3802 rec.read(e1.num_rows_device());
3803 rec.read(e2.num_rows_device());
3804 rec.read(e3.num_rows_device());
3805 rec.read(e4.num_rows_device());
3806 rec.read_column(e1.column(0).expect("e1.col0"));
3807 rec.read_column(e1.column(1).expect("e1.col1"));
3808 rec.read_column(e2.column(1).expect("e2.col1"));
3809 rec.read_column(e3.column(0).expect("e3.col0"));
3810 rec.read_column(e3.column(1).expect("e3.col1"));
3811 rec.read_column(e4.column(0).expect("e4.col0"));
3812 rec.read_column(e4.column(1).expect("e4.col1"));
3813 rec.read(&plan.e1_work_prefix);
3814 rec.read(&plan.e2_work_prefix);
3815 rec.read(&plan.e1_e2_start);
3816 rec.read(&plan.e1_e2_end);
3817 rec.write(&row_counts);
3818 rec.write(&row_agg);
3819 rec.preflight(runtime)
3820 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
3821 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
3822 if matches!(agg_op, AggOp::Min) {
3823 let fill = self
3825 .device()
3826 .inner()
3827 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U32)
3828 .ok_or_else(|| {
3829 XlogError::Kernel("arith_fill_const_u32 kernel not found".to_string())
3830 })?;
3831 let row_agg_u32 = &mut reinterpret_u8_as_u32(&mut row_agg)?;
3832 unsafe {
3834 fill.clone()
3835 .launch_in(
3836 stream,
3837 LaunchConfig::for_num_elems(n_e1),
3838 (u32::MAX, n_e1, &mut *row_agg_u32),
3839 )
3840 .map_err(|e| {
3841 XlogError::Kernel(format!("{ctx}: min identity fill failed: {e}"))
3842 })?;
3843 }
3844 }
3845 {
3846 let kernel = self
3847 .device()
3848 .inner()
3849 .get_func(WCOJ_MODULE, kernel_name)
3850 .ok_or_else(|| XlogError::Kernel(format!("{kernel_name} kernel not found")))?;
3851 let mut params: Vec<*mut c_void> = vec![
3852 e1_col0.as_kernel_param(),
3853 e1_col1.as_kernel_param(),
3854 plan.row_count.as_kernel_param(),
3855 e2_col1.as_kernel_param(),
3856 e3_col0.as_kernel_param(),
3857 e3_col1.as_kernel_param(),
3858 n_e3.as_kernel_param(),
3859 e4_col0.as_kernel_param(),
3860 e4_col1.as_kernel_param(),
3861 n_e4.as_kernel_param(),
3862 value_sel.as_kernel_param(),
3863 (&plan.e1_work_prefix).as_kernel_param(),
3864 (&plan.e2_work_prefix).as_kernel_param(),
3865 (&plan.e1_e2_start).as_kernel_param(),
3866 (&plan.e1_e2_end).as_kernel_param(),
3867 plan.total_work.as_kernel_param(),
3868 plan.block_work_unit.as_kernel_param(),
3869 (&row_counts).as_kernel_param(),
3870 (&row_agg).as_kernel_param(),
3871 ];
3872 unsafe {
3873 kernel
3874 .clone()
3875 .launch_in(
3876 stream,
3877 LaunchConfig {
3878 grid_dim: (grid, 1, 1),
3879 block_dim: (BLOCK_SIZE, 1, 1),
3880 shared_mem_bytes: 0,
3881 },
3882 &mut params,
3883 )
3884 .map_err(|e| {
3885 XlogError::Kernel(format!("{ctx}: groupby-agg launch failed: {e}"))
3886 })?;
3887 }
3888 }
3889 Ok(())
3890 };
3891 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
3893 .map_err(|error| error.into_xlog_error())?;
3894
3895 rec.commit()
3896 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
3897
3898 let w_src = match e1.column(0).expect("e1.col0") {
3902 CudaColumn::Owned(slice) => slice,
3903 _ => {
3904 return Err(XlogError::Kernel(format!(
3905 "{ctx}: e1.col0 must be an owned CudaColumn"
3906 )))
3907 }
3908 };
3909 let w_copy = self
3910 .memory()
3911 .alloc::<u8>(n_e1 as usize * std::mem::size_of::<u32>())?;
3912 unsafe {
3916 let res = sys::cuMemcpyDtoD_v2(
3917 *w_copy.device_ptr(),
3918 *w_src.device_ptr(),
3919 n_e1 as usize * std::mem::size_of::<u32>(),
3920 );
3921 if res != sys::cudaError_enum::CUDA_SUCCESS {
3922 return Err(XlogError::Kernel(format!(
3923 "{ctx}: copy W column failed: {res:?}"
3924 )));
3925 }
3926 }
3927 let mut d_num_rows = self.memory().alloc::<u32>(1)?;
3928 self.device()
3929 .inner()
3930 .dtod_copy(e1.num_rows_device(), &mut d_num_rows)
3931 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy row count failed: {e}")))?;
3932 let staging_schema = Schema::new(vec![
3933 ("w".to_string(), w_type),
3934 ("count".to_string(), ScalarType::U32),
3935 ("agg".to_string(), agg_scalar),
3936 ]);
3937 let staging = CudaBuffer::from_columns_with_host_count(
3938 vec![w_copy.into(), row_counts.into(), row_agg.into()],
3939 n_e1 as u64,
3940 d_num_rows,
3941 staging_schema,
3942 n_e1,
3943 );
3944
3945 let mask = self.compare_const_mask_recorded::<u32>(
3948 &staging,
3949 1,
3950 0u32,
3951 crate::CompareOp::Gt,
3952 launch_stream,
3953 )?;
3954 let compacted =
3955 self.compact_buffer_by_device_mask_counted_recorded(&staging, &mask, launch_stream)?;
3956 self.groupby_multi_agg_recorded(&compacted, &[0], &[(2, agg_op)], launch_stream)
3957 }
3958
3959 pub fn wcoj_4cycle_groupby_root_count_u64_recorded(
3983 &self,
3984 e1: &CudaBuffer,
3985 e2: &CudaBuffer,
3986 e3: &CudaBuffer,
3987 e4: &CudaBuffer,
3988 block_work_unit: u32,
3989 launch_stream: StreamId,
3990 ) -> Result<CudaBuffer> {
3991 let ctx = "wcoj_4cycle_groupby_root_count_u64_recorded";
3992 let e1 = &self.wcoj_layout_u64_recorded(e1, launch_stream)?;
3998 let e2 = &self.wcoj_layout_u64_recorded(e2, launch_stream)?;
3999 let e3 = &self.wcoj_layout_u64_recorded(e3, launch_stream)?;
4000 let e4 = &self.wcoj_layout_u64_recorded(e4, launch_stream)?;
4001 validate_binary_u64(ctx, "e1", e1)?;
4002 validate_binary_u64(ctx, "e2", e2)?;
4003 validate_binary_u64(ctx, "e3", e3)?;
4004 validate_binary_u64(ctx, "e4", e4)?;
4005 let plan = self.wcoj_4cycle_hg_work_plan_u64_recorded(
4006 e1,
4007 e2,
4008 e3,
4009 e4,
4010 block_work_unit,
4011 launch_stream,
4012 )?;
4013 let n_e1 = plan.row_count;
4014 let out_schema = Schema::new(vec![
4015 ("w".to_string(), ScalarType::U64),
4016 ("count".to_string(), ScalarType::U64),
4017 ]);
4018 if n_e1 == 0 || plan.total_work == 0 {
4019 return self.create_empty_buffer(out_schema);
4020 }
4021
4022 let runtime = self.memory().runtime().ok_or_else(|| {
4023 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
4024 })?;
4025 let cu_stream = runtime
4026 .stream_pool()
4027 .resolve(launch_stream)
4028 .ok_or_else(|| {
4029 XlogError::Kernel(format!(
4030 "{ctx}: launch_stream StreamId({}) does not resolve",
4031 launch_stream.0
4032 ))
4033 })?;
4034
4035 let e1_col0 = &metadata_column_u64(e1, 0)?;
4036 let e1_col1 = &metadata_column_u64(e1, 1)?;
4037 let e2_col1 = &metadata_column_u64(e2, 1)?;
4038 let e3_col0 = &metadata_column_u64(e3, 0)?;
4039 let e3_col1 = &metadata_column_u64(e3, 1)?;
4040 let e4_col0 = &metadata_column_u64(e4, 0)?;
4041 let e4_col1 = &metadata_column_u64(e4, 1)?;
4042 let n_e3 = self.metadata_logical_rows(e3)?;
4043 let n_e4 = self.metadata_logical_rows(e4)?;
4044
4045 let mut row_counts = self.memory().alloc::<u32>(n_e1 as usize)?;
4047 self.device()
4048 .inner()
4049 .memset_zeros(&mut row_counts)
4050 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero row counts failed: {e}")))?;
4051
4052 let grid = plan.total_work.div_ceil(plan.block_work_unit);
4053 let mut rec = LaunchRecorder::new_strict(launch_stream);
4054 rec.read(e1.num_rows_device());
4055 rec.read(e2.num_rows_device());
4056 rec.read(e3.num_rows_device());
4057 rec.read(e4.num_rows_device());
4058 rec.read_column(e1.column(0).expect("e1.col0"));
4059 rec.read_column(e1.column(1).expect("e1.col1"));
4060 rec.read_column(e2.column(1).expect("e2.col1"));
4061 rec.read_column(e3.column(0).expect("e3.col0"));
4062 rec.read_column(e3.column(1).expect("e3.col1"));
4063 rec.read_column(e4.column(0).expect("e4.col0"));
4064 rec.read_column(e4.column(1).expect("e4.col1"));
4065 rec.read(&plan.e1_work_prefix);
4066 rec.read(&plan.e2_work_prefix);
4067 rec.read(&plan.e1_e2_start);
4068 rec.read(&plan.e1_e2_end);
4069 rec.write(&row_counts);
4070 rec.preflight(runtime)
4071 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
4072 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
4073 {
4074 let kernel = self
4075 .device()
4076 .inner()
4077 .get_func(
4078 WCOJ_MODULE,
4079 wcoj_kernels::WCOJ_4CYCLE_GROUPBY_ROOT_COUNT_HG_U64,
4080 )
4081 .ok_or_else(|| {
4082 XlogError::Kernel(
4083 "wcoj_4cycle_groupby_root_count_hg_u64 kernel not found".to_string(),
4084 )
4085 })?;
4086 let mut params: Vec<*mut c_void> = vec![
4087 e1_col0.as_kernel_param(),
4088 e1_col1.as_kernel_param(),
4089 plan.row_count.as_kernel_param(),
4090 e2_col1.as_kernel_param(),
4091 e3_col0.as_kernel_param(),
4092 e3_col1.as_kernel_param(),
4093 n_e3.as_kernel_param(),
4094 e4_col0.as_kernel_param(),
4095 e4_col1.as_kernel_param(),
4096 n_e4.as_kernel_param(),
4097 (&plan.e1_work_prefix).as_kernel_param(),
4098 (&plan.e2_work_prefix).as_kernel_param(),
4099 (&plan.e1_e2_start).as_kernel_param(),
4100 (&plan.e1_e2_end).as_kernel_param(),
4101 plan.total_work.as_kernel_param(),
4102 plan.block_work_unit.as_kernel_param(),
4103 (&row_counts).as_kernel_param(),
4104 ];
4105 unsafe {
4106 kernel
4107 .clone()
4108 .launch_in(
4109 stream,
4110 LaunchConfig {
4111 grid_dim: (grid, 1, 1),
4112 block_dim: (BLOCK_SIZE, 1, 1),
4113 shared_mem_bytes: 0,
4114 },
4115 &mut params,
4116 )
4117 .map_err(|e| {
4118 XlogError::Kernel(format!("{ctx}: groupby-count launch failed: {e}"))
4119 })?;
4120 }
4121 }
4122 Ok(())
4123 };
4124 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
4126 .map_err(|error| error.into_xlog_error())?;
4127
4128 rec.commit()
4129 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
4130
4131 let meta = self.wcoj_build_metadata_u64_recorded(e1, 0, launch_stream)?;
4135 let key_count = meta.key_count;
4136 if key_count == 0 {
4137 return self.create_empty_buffer(out_schema);
4138 }
4139 let mut sums = self
4140 .memory()
4141 .alloc::<u8>(key_count as usize * std::mem::size_of::<u64>())?;
4142 self.device()
4143 .inner()
4144 .memset_zeros(&mut sums)
4145 .map_err(|e| XlogError::Kernel(format!("{ctx}: zero group sums failed: {e}")))?;
4146
4147 let mut rec_sum = LaunchRecorder::new_strict(launch_stream);
4148 rec_sum.read(&row_counts);
4149 rec_sum.read(&meta.prefix_sum);
4150 rec_sum.write(&sums);
4151 rec_sum
4152 .preflight(runtime)
4153 .map_err(|e| XlogError::Kernel(format!("{ctx}: reduce preflight failed: {e}")))?;
4154 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
4155 {
4156 let kernel = self
4157 .device()
4158 .inner()
4159 .get_func(
4160 WCOJ_MODULE,
4161 wcoj_kernels::WCOJ_GROUPBY_ROOT_SEGMENT_SUM_COUNTS_U32,
4162 )
4163 .ok_or_else(|| {
4164 XlogError::Kernel(
4165 "wcoj_groupby_root_segment_sum_counts_u32 kernel not found".to_string(),
4166 )
4167 })?;
4168 let reduce_grid = n_e1.div_ceil(BLOCK_SIZE);
4169 let mut params: Vec<*mut c_void> = vec![
4170 (&row_counts).as_kernel_param(),
4171 n_e1.as_kernel_param(),
4172 (&meta.prefix_sum).as_kernel_param(),
4173 key_count.as_kernel_param(),
4174 (&sums).as_kernel_param(),
4175 ];
4176 unsafe {
4177 kernel
4178 .clone()
4179 .launch_in(
4180 stream,
4181 LaunchConfig {
4182 grid_dim: (reduce_grid, 1, 1),
4183 block_dim: (BLOCK_SIZE, 1, 1),
4184 shared_mem_bytes: 0,
4185 },
4186 &mut params,
4187 )
4188 .map_err(|e| {
4189 XlogError::Kernel(format!("{ctx}: reduce launch failed: {e}"))
4190 })?;
4191 }
4192 }
4193 Ok(())
4194 };
4195 let rec_sum = unsafe { rec_sum.enqueue_prepared_with(&cu_stream, enqueue) }
4197 .map_err(|error| error.into_xlog_error())?;
4198
4199 rec_sum
4200 .commit()
4201 .map_err(|e| XlogError::Kernel(format!("{ctx}: reduce commit failed: {e}")))?;
4202
4203 let w_copy = self
4209 .memory()
4210 .alloc::<u8>(key_count as usize * std::mem::size_of::<u64>())?;
4211 let d_num_rows = self.memory().alloc::<u32>(1)?;
4212 let mut rec_copy = LaunchRecorder::new_strict(launch_stream);
4213 rec_copy.read(&meta.unique_keys);
4214 rec_copy.write(&w_copy);
4215 rec_copy.write(&d_num_rows);
4216 rec_copy
4217 .preflight(runtime)
4218 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy preflight failed: {e}")))?;
4219 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
4220 unsafe {
4221 let res = sys::cuMemcpyDtoDAsync_v2(
4222 *w_copy.device_ptr(),
4223 *meta.unique_keys.device_ptr(),
4224 key_count as usize * std::mem::size_of::<u64>(),
4225 stream.stream().cu_stream(),
4226 );
4227 if res != sys::cudaError_enum::CUDA_SUCCESS {
4228 return Err(XlogError::Kernel(format!(
4229 "{ctx}: DtoD unique keys copy failed: {res:?}"
4230 )));
4231 }
4232 }
4233 self.initialize_launch_metadata_u32(
4234 key_count,
4235 &d_num_rows,
4236 stream,
4237 &format!("{ctx}: d_num_rows"),
4238 )?;
4239 Ok(())
4240 };
4241 let rec_copy = unsafe { rec_copy.enqueue_prepared_with(&cu_stream, enqueue) }
4243 .map_err(|error| error.into_xlog_error())?;
4244
4245 rec_copy
4246 .commit()
4247 .map_err(|e| XlogError::Kernel(format!("{ctx}: copy commit failed: {e}")))?;
4248 cu_stream
4249 .synchronize()
4250 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
4251 let staging_schema = Schema::new(vec![
4252 ("w".to_string(), ScalarType::U64),
4253 ("count".to_string(), ScalarType::U64),
4254 ]);
4255 let staging = CudaBuffer::from_columns_with_host_count(
4256 vec![w_copy.into(), sums.into()],
4257 u64::from(key_count),
4258 d_num_rows,
4259 staging_schema,
4260 key_count,
4261 );
4262 let mask = self.compare_const_mask_recorded::<u64>(
4263 &staging,
4264 1,
4265 0u64,
4266 crate::CompareOp::Gt,
4267 launch_stream,
4268 )?;
4269 self.compact_buffer_by_device_mask_counted_recorded(&staging, &mask, launch_stream)
4270 }
4271
4272 pub fn wcoj_4cycle_hg_work_plan_u64_recorded(
4273 &self,
4274 e1: &CudaBuffer,
4275 e2: &CudaBuffer,
4276 e3: &CudaBuffer,
4277 e4: &CudaBuffer,
4278 block_work_unit: u32,
4279 launch_stream: StreamId,
4280 ) -> Result<WcojCycle4HgWorkPlanU64> {
4281 let ctx = "wcoj_4cycle_hg_work_plan_u64_recorded";
4282 if block_work_unit == 0 {
4283 return Err(XlogError::Kernel(format!(
4284 "{ctx}: block_work_unit must be nonzero"
4285 )));
4286 }
4287 validate_binary_u64(ctx, "e1", e1)?;
4288 validate_binary_u64(ctx, "e2", e2)?;
4289 validate_binary_u64(ctx, "e3", e3)?;
4290 validate_binary_u64(ctx, "e4", e4)?;
4291
4292 let n_e1 = self.metadata_logical_rows(e1)?;
4293 let n_e2 = self.metadata_logical_rows(e2)?;
4294 let n_e3 = self.metadata_logical_rows(e3)?;
4295 let prefix_len = n_e1
4296 .checked_add(1)
4297 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: prefix length overflow")))?;
4298 let e2_prefix_len = n_e2
4299 .checked_add(1)
4300 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: e2 prefix length overflow")))?;
4301 let mut e1_work_prefix = self.memory().alloc::<u32>(prefix_len as usize)?;
4302 let mut e2_work_prefix = self.memory().alloc::<u32>(e2_prefix_len as usize)?;
4303 let mut e1_e2_start = self.memory().alloc::<u32>(n_e1 as usize)?;
4304 let mut e1_e2_end = self.memory().alloc::<u32>(n_e1 as usize)?;
4305
4306 if n_e1 == 0 || n_e2 == 0 || n_e3 == 0 || self.metadata_logical_rows(e4)? == 0 {
4307 let block_counts = self.memory().alloc::<u32>(1)?;
4308 let block_offsets = self.memory().alloc::<u32>(1)?;
4309 return Ok(WcojCycle4HgWorkPlanU64 {
4310 e1_work_prefix,
4311 e2_work_prefix,
4312 e1_e2_start,
4313 e1_e2_end,
4314 block_counts,
4315 block_offsets,
4316 total_work: 0,
4317 block_work_unit,
4318 row_count: n_e1,
4319 });
4320 }
4321
4322 let runtime = self.memory().runtime().ok_or_else(|| {
4323 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
4324 })?;
4325 let cu_stream = runtime
4326 .stream_pool()
4327 .resolve(launch_stream)
4328 .ok_or_else(|| {
4329 XlogError::Kernel(format!(
4330 "{ctx}: launch_stream StreamId({}) does not resolve",
4331 launch_stream.0
4332 ))
4333 })?;
4334
4335 let e1_col1 = &metadata_column_u64(e1, 1)?;
4336 let e2_col0 = &metadata_column_u64(e2, 0)?;
4337 let e2_col1 = &metadata_column_u64(e2, 1)?;
4338 let e3_col0 = &metadata_column_u64(e3, 0)?;
4339
4340 let mut scan_scratch =
4341 self.multiblock_scan_u32_scratch_for_len(e2_prefix_len.max(prefix_len))?;
4342 let mut rec = LaunchRecorder::new_strict(launch_stream);
4343 rec.read(e1.num_rows_device());
4344 rec.read(e2.num_rows_device());
4345 rec.read(e3.num_rows_device());
4346 rec.read_column(e1.column(1).expect("e1.col1"));
4347 rec.read_column(e2.column(0).expect("e2.col0"));
4348 rec.read_column(e2.column(1).expect("e2.col1"));
4349 rec.read_column(e3.column(0).expect("e3.col0"));
4350 rec.read_write(&e2_work_prefix);
4351 rec.write(&e1_work_prefix);
4352 rec.write(&e1_e2_start);
4353 rec.write(&e1_e2_end);
4354 for level in scan_scratch.levels() {
4355 rec.read_write(level);
4356 }
4357 rec.preflight(runtime)
4358 .map_err(|e| XlogError::Kernel(format!("{ctx}: preflight failed: {e}")))?;
4359 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
4360 let e2_kernel = self
4361 .device()
4362 .inner()
4363 .get_func(
4364 WCOJ_MODULE,
4365 wcoj_kernels::WCOJ_4CYCLE_BUILD_E2_WORK_PREFIX_U64,
4366 )
4367 .ok_or_else(|| {
4368 XlogError::Kernel(
4369 "wcoj_4cycle_build_e2_work_prefix_u64 kernel not found".to_string(),
4370 )
4371 })?;
4372 let e2_grid = n_e2.div_ceil(BLOCK_SIZE);
4373 unsafe {
4374 e2_kernel
4375 .clone()
4376 .launch_in(
4377 stream,
4378 LaunchConfig {
4379 grid_dim: (e2_grid, 1, 1),
4380 block_dim: (BLOCK_SIZE, 1, 1),
4381 shared_mem_bytes: 0,
4382 },
4383 (e2_col1, n_e2, e3_col0, n_e3, &mut e2_work_prefix),
4384 )
4385 .map_err(|e| {
4386 XlogError::Kernel(format!(
4387 "wcoj_4cycle_build_e2_work_prefix_u64 launch failed: {e}"
4388 ))
4389 })?;
4390 }
4391 self.multiblock_scan_u32_inplace_on_stream(
4392 &mut e2_work_prefix,
4393 e2_prefix_len,
4394 stream,
4395 &mut scan_scratch,
4396 )?;
4397
4398 let kernel = self
4399 .device()
4400 .inner()
4401 .get_func(
4402 WCOJ_MODULE,
4403 wcoj_kernels::WCOJ_4CYCLE_BUILD_HG_WORK_PLAN_U64,
4404 )
4405 .ok_or_else(|| {
4406 XlogError::Kernel(
4407 "wcoj_4cycle_build_hg_work_plan_u64 kernel not found".to_string(),
4408 )
4409 })?;
4410 let grid = n_e1.div_ceil(BLOCK_SIZE);
4411 unsafe {
4412 kernel
4413 .clone()
4414 .launch_in(
4415 stream,
4416 LaunchConfig {
4417 grid_dim: (grid, 1, 1),
4418 block_dim: (BLOCK_SIZE, 1, 1),
4419 shared_mem_bytes: 0,
4420 },
4421 (
4422 e1_col1,
4423 n_e1,
4424 e2_col0,
4425 n_e2,
4426 &e2_work_prefix,
4427 &mut e1_work_prefix,
4428 &mut e1_e2_start,
4429 &mut e1_e2_end,
4430 ),
4431 )
4432 .map_err(|e| {
4433 XlogError::Kernel(format!(
4434 "wcoj_4cycle_build_hg_work_plan_u64 launch failed: {e}"
4435 ))
4436 })?;
4437 }
4438 self.multiblock_scan_u32_inplace_on_stream(
4439 &mut e1_work_prefix,
4440 prefix_len,
4441 stream,
4442 &mut scan_scratch,
4443 )?;
4444 Ok(())
4445 };
4446 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
4448 .map_err(|error| error.into_xlog_error())?;
4449
4450 rec.commit()
4451 .map_err(|e| XlogError::Kernel(format!("{ctx}: commit failed: {e}")))?;
4452 cu_stream
4453 .synchronize()
4454 .map_err(|e| XlogError::Kernel(format!("{ctx}: stream sync failed: {e}")))?;
4455 let total_work = self.dtoh_scalar_untracked::<u32>(&e1_work_prefix, n_e1 as usize)?;
4456 let grid = if total_work == 0 {
4457 1
4458 } else {
4459 total_work.div_ceil(block_work_unit)
4460 };
4461 let block_counts = self.memory().alloc::<u32>(grid as usize)?;
4462 let block_offsets = self.memory().alloc::<u32>(grid as usize)?;
4463
4464 Ok(WcojCycle4HgWorkPlanU64 {
4465 e1_work_prefix,
4466 e2_work_prefix,
4467 e1_e2_start,
4468 e1_e2_end,
4469 block_counts,
4470 block_offsets,
4471 total_work,
4472 block_work_unit,
4473 row_count: n_e1,
4474 })
4475 }
4476
4477 pub fn wcoj_4cycle_hg_u64_recorded(
4478 &self,
4479 e1: &CudaBuffer,
4480 e2: &CudaBuffer,
4481 e3: &CudaBuffer,
4482 e4: &CudaBuffer,
4483 block_work_unit: u32,
4484 launch_stream: StreamId,
4485 ) -> Result<CudaBuffer> {
4486 let ctx = "wcoj_4cycle_hg_u64_recorded";
4487 validate_binary_u64(ctx, "e1", e1)?;
4488 validate_binary_u64(ctx, "e2", e2)?;
4489 validate_binary_u64(ctx, "e3", e3)?;
4490 validate_binary_u64(ctx, "e4", e4)?;
4491 let plan = self.wcoj_4cycle_hg_work_plan_u64_recorded(
4492 e1,
4493 e2,
4494 e3,
4495 e4,
4496 block_work_unit,
4497 launch_stream,
4498 )?;
4499 let out_schema = Schema::new(vec![
4500 ("col0".to_string(), ScalarType::U64),
4501 ("col1".to_string(), ScalarType::U64),
4502 ("col2".to_string(), ScalarType::U64),
4503 ("col3".to_string(), ScalarType::U64),
4504 ]);
4505 if plan.total_work == 0 {
4506 return self.create_empty_buffer(out_schema);
4507 }
4508
4509 let grid = plan.total_work.div_ceil(plan.block_work_unit);
4510 let bytes_count = (grid as usize)
4511 .checked_mul(std::mem::size_of::<u32>())
4512 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: count byte size overflow")))?;
4513 let mut local_counts = None;
4514 let mut local_offsets = None;
4515 if grid > 1024 {
4516 local_counts = Some(self.memory().alloc::<u32>(grid as usize)?);
4517 local_offsets = Some(self.memory().alloc::<u32>(grid as usize)?);
4518 }
4519 let total_rows_device = self.memory().alloc::<u32>(1)?;
4520
4521 let runtime = self.memory().runtime().ok_or_else(|| {
4522 XlogError::Kernel(format!("{ctx} requires a runtime-backed GpuMemoryManager"))
4523 })?;
4524 let cu_stream = runtime
4525 .stream_pool()
4526 .resolve(launch_stream)
4527 .ok_or_else(|| {
4528 XlogError::Kernel(format!(
4529 "{ctx}: launch_stream StreamId({}) does not resolve",
4530 launch_stream.0
4531 ))
4532 })?;
4533
4534 let e1_col0 = &metadata_column_u64(e1, 0)?;
4535 let e1_col1 = &metadata_column_u64(e1, 1)?;
4536 let e2_col1 = &metadata_column_u64(e2, 1)?;
4537 let e3_col0 = &metadata_column_u64(e3, 0)?;
4538 let e3_col1 = &metadata_column_u64(e3, 1)?;
4539 let e4_col0 = &metadata_column_u64(e4, 0)?;
4540 let e4_col1 = &metadata_column_u64(e4, 1)?;
4541 let n_e3 = self.metadata_logical_rows(e3)?;
4542 let n_e4 = self.metadata_logical_rows(e4)?;
4543
4544 let count_u32 = if grid <= 1024 {
4545 &plan.block_counts
4546 } else {
4547 local_counts
4548 .as_ref()
4549 .expect("local HG counts allocated when grid exceeds single-block scan")
4550 };
4551 let mut scan_scratch =
4552 self.multiblock_scan_u32_scratch_for_len(if grid > 1024 { grid } else { 0 })?;
4553 let mut rec_hg = LaunchRecorder::new_strict(launch_stream);
4554 rec_hg.read(e1.num_rows_device());
4555 rec_hg.read(e2.num_rows_device());
4556 rec_hg.read(e3.num_rows_device());
4557 rec_hg.read(e4.num_rows_device());
4558 rec_hg.read_column(e1.column(0).expect("e1.col0"));
4559 rec_hg.read_column(e1.column(1).expect("e1.col1"));
4560 rec_hg.read_column(e2.column(1).expect("e2.col1"));
4561 rec_hg.read_column(e3.column(0).expect("e3.col0"));
4562 rec_hg.read_column(e3.column(1).expect("e3.col1"));
4563 rec_hg.read_column(e4.column(0).expect("e4.col0"));
4564 rec_hg.read_column(e4.column(1).expect("e4.col1"));
4565 rec_hg.read(&plan.e1_work_prefix);
4566 rec_hg.read(&plan.e2_work_prefix);
4567 rec_hg.read(&plan.e1_e2_start);
4568 rec_hg.read(&plan.e1_e2_end);
4569 rec_hg.read_write(count_u32);
4570 if grid <= 1024 {
4571 rec_hg.read_write(&plan.block_offsets);
4572 } else {
4573 rec_hg.read_write(
4574 local_offsets
4575 .as_ref()
4576 .expect("local HG offsets allocated when grid exceeds single-block scan"),
4577 );
4578 }
4579 rec_hg.write(&total_rows_device);
4580 for level in scan_scratch.levels() {
4581 rec_hg.read_write(level);
4582 }
4583 rec_hg
4584 .preflight(runtime)
4585 .map_err(|e| XlogError::Kernel(format!("{ctx}: HG preflight failed: {e}")))?;
4586 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
4587 {
4588 let kernel = self
4589 .device()
4590 .inner()
4591 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_4CYCLE_COUNT_HG_U64)
4592 .ok_or_else(|| {
4593 XlogError::Kernel("wcoj_4cycle_count_hg_u64 kernel not found".to_string())
4594 })?;
4595 let mut params: Vec<*mut c_void> = vec![
4596 e1_col0.as_kernel_param(),
4597 e1_col1.as_kernel_param(),
4598 plan.row_count.as_kernel_param(),
4599 e2_col1.as_kernel_param(),
4600 e3_col0.as_kernel_param(),
4601 e3_col1.as_kernel_param(),
4602 n_e3.as_kernel_param(),
4603 e4_col0.as_kernel_param(),
4604 e4_col1.as_kernel_param(),
4605 n_e4.as_kernel_param(),
4606 (&plan.e1_work_prefix).as_kernel_param(),
4607 (&plan.e2_work_prefix).as_kernel_param(),
4608 (&plan.e1_e2_start).as_kernel_param(),
4609 (&plan.e1_e2_end).as_kernel_param(),
4610 plan.total_work.as_kernel_param(),
4611 plan.block_work_unit.as_kernel_param(),
4612 count_u32.as_kernel_param(),
4613 ];
4614 unsafe {
4615 kernel
4616 .clone()
4617 .launch_in(
4618 stream,
4619 LaunchConfig {
4620 grid_dim: (grid, 1, 1),
4621 block_dim: (BLOCK_SIZE, 1, 1),
4622 shared_mem_bytes: 0,
4623 },
4624 &mut params,
4625 )
4626 .map_err(|e| {
4627 XlogError::Kernel(format!("{ctx}: count launch failed: {e}"))
4628 })?;
4629 }
4630 }
4631 if grid <= 1024 {
4632 let kernel = self
4633 .device()
4634 .inner()
4635 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_SCAN_HG_BLOCK_COUNTS_U32)
4636 .ok_or_else(|| {
4637 XlogError::Kernel(
4638 "wcoj_scan_hg_block_counts_u32 kernel not found".to_string(),
4639 )
4640 })?;
4641 let mut params: Vec<*mut c_void> = vec![
4642 count_u32.as_kernel_param(),
4643 grid.as_kernel_param(),
4644 (&plan.block_offsets).as_kernel_param(),
4645 (&total_rows_device).as_kernel_param(),
4646 ];
4647 unsafe {
4648 kernel
4649 .clone()
4650 .launch_in(
4651 stream,
4652 LaunchConfig {
4653 grid_dim: (1, 1, 1),
4654 block_dim: (1024, 1, 1),
4655 shared_mem_bytes: 0,
4656 },
4657 &mut params,
4658 )
4659 .map_err(|e| XlogError::Kernel(format!("{ctx}: scan failed: {e}")))?;
4660 }
4661 } else {
4662 let offsets_mut = local_offsets
4663 .as_mut()
4664 .expect("local HG offsets allocated when grid exceeds single-block scan");
4665 unsafe {
4666 let res = sys::cuMemcpyDtoDAsync_v2(
4667 *offsets_mut.device_ptr(),
4668 *count_u32.device_ptr(),
4669 bytes_count,
4670 stream.stream().cu_stream(),
4671 );
4672 if res != sys::cudaError_enum::CUDA_SUCCESS {
4673 return Err(XlogError::Kernel(format!(
4674 "{ctx}: DtoD count to offsets failed: {res:?}"
4675 )));
4676 }
4677 }
4678 self.multiblock_scan_u32_inplace_on_stream(
4679 offsets_mut,
4680 grid,
4681 stream,
4682 &mut scan_scratch,
4683 )?;
4684 let total_kernel = self
4685 .device()
4686 .inner()
4687 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_COMPUTE_TOTAL)
4688 .ok_or_else(|| {
4689 XlogError::Kernel("wcoj_compute_total kernel not found".to_string())
4690 })?;
4691 let mut params: Vec<*mut c_void> = vec![
4692 count_u32.as_kernel_param(),
4693 (&*offsets_mut).as_kernel_param(),
4694 grid.as_kernel_param(),
4695 (&total_rows_device).as_kernel_param(),
4696 ];
4697 unsafe {
4698 total_kernel
4699 .clone()
4700 .launch_in(
4701 stream,
4702 LaunchConfig {
4703 grid_dim: (1, 1, 1),
4704 block_dim: (1, 1, 1),
4705 shared_mem_bytes: 0,
4706 },
4707 &mut params,
4708 )
4709 .map_err(|e| XlogError::Kernel(format!("{ctx}: total failed: {e}")))?;
4710 }
4711 }
4712 Ok(())
4713 };
4714 let rec_hg = unsafe { rec_hg.enqueue_prepared_with(&cu_stream, enqueue) }
4716 .map_err(|error| error.into_xlog_error())?;
4717
4718 rec_hg
4719 .commit()
4720 .map_err(|e| XlogError::Kernel(format!("{ctx}: count commit failed: {e}")))?;
4721 cu_stream
4722 .synchronize()
4723 .map_err(|e| XlogError::Kernel(format!("{ctx}: count stream sync failed: {e}")))?;
4724 let total_rows = self
4725 .dtoh_scalar_untracked::<u32>(&total_rows_device, 0)
4726 .map_err(|e| XlogError::Kernel(format!("{ctx}: read total rows failed: {e}")))?;
4727 if total_rows == 0 {
4728 return self.create_empty_buffer(out_schema);
4729 }
4730
4731 let bytes_per_col = (total_rows as usize)
4732 .checked_mul(std::mem::size_of::<u64>())
4733 .ok_or_else(|| XlogError::Kernel(format!("{ctx}: output byte size overflow")))?;
4734 let mut out_w = self.memory().alloc::<u8>(bytes_per_col)?;
4735 let mut out_x = self.memory().alloc::<u8>(bytes_per_col)?;
4736 let mut out_y = self.memory().alloc::<u8>(bytes_per_col)?;
4737 let mut out_z = self.memory().alloc::<u8>(bytes_per_col)?;
4738 let materialize_offsets = if grid <= 1024 {
4739 &plan.block_offsets
4740 } else {
4741 local_offsets
4742 .as_ref()
4743 .expect("local HG offsets allocated when grid exceeds single-block scan")
4744 };
4745 let mut rec_mat = LaunchRecorder::new_strict(launch_stream);
4746 rec_mat.read(materialize_offsets);
4747 rec_mat.read(e1.num_rows_device());
4748 rec_mat.read(e2.num_rows_device());
4749 rec_mat.read(e3.num_rows_device());
4750 rec_mat.read(e4.num_rows_device());
4751 rec_mat.read_column(e1.column(0).expect("e1.col0"));
4752 rec_mat.read_column(e1.column(1).expect("e1.col1"));
4753 rec_mat.read_column(e2.column(1).expect("e2.col1"));
4754 rec_mat.read_column(e3.column(0).expect("e3.col0"));
4755 rec_mat.read_column(e3.column(1).expect("e3.col1"));
4756 rec_mat.read_column(e4.column(0).expect("e4.col0"));
4757 rec_mat.read_column(e4.column(1).expect("e4.col1"));
4758 rec_mat.read(&plan.e1_work_prefix);
4759 rec_mat.read(&plan.e2_work_prefix);
4760 rec_mat.read(&plan.e1_e2_start);
4761 rec_mat.read(&plan.e1_e2_end);
4762 rec_mat.write(&out_w);
4763 rec_mat.write(&out_x);
4764 rec_mat.write(&out_y);
4765 rec_mat.write(&out_z);
4766 rec_mat
4767 .preflight(runtime)
4768 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize preflight failed: {e}")))?;
4769 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
4770 {
4771 let kernel = self
4772 .device()
4773 .inner()
4774 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_4CYCLE_MATERIALIZE_HG_U64)
4775 .ok_or_else(|| {
4776 XlogError::Kernel(
4777 "wcoj_4cycle_materialize_hg_u64 kernel not found".to_string(),
4778 )
4779 })?;
4780 let out_w_u64 = &mut reinterpret_u8_as_u64(&mut out_w)?;
4781 let out_x_u64 = &mut reinterpret_u8_as_u64(&mut out_x)?;
4782 let out_y_u64 = &mut reinterpret_u8_as_u64(&mut out_y)?;
4783 let out_z_u64 = &mut reinterpret_u8_as_u64(&mut out_z)?;
4784 let mut params: Vec<*mut c_void> = vec![
4785 e1_col0.as_kernel_param(),
4786 e1_col1.as_kernel_param(),
4787 plan.row_count.as_kernel_param(),
4788 e2_col1.as_kernel_param(),
4789 e3_col0.as_kernel_param(),
4790 e3_col1.as_kernel_param(),
4791 n_e3.as_kernel_param(),
4792 e4_col0.as_kernel_param(),
4793 e4_col1.as_kernel_param(),
4794 n_e4.as_kernel_param(),
4795 (&plan.e1_work_prefix).as_kernel_param(),
4796 (&plan.e2_work_prefix).as_kernel_param(),
4797 (&plan.e1_e2_start).as_kernel_param(),
4798 (&plan.e1_e2_end).as_kernel_param(),
4799 plan.total_work.as_kernel_param(),
4800 plan.block_work_unit.as_kernel_param(),
4801 materialize_offsets.as_kernel_param(),
4802 total_rows.as_kernel_param(),
4803 out_w_u64.as_kernel_param(),
4804 out_x_u64.as_kernel_param(),
4805 out_y_u64.as_kernel_param(),
4806 out_z_u64.as_kernel_param(),
4807 ];
4808 unsafe {
4809 kernel
4810 .clone()
4811 .launch_in(
4812 stream,
4813 LaunchConfig {
4814 grid_dim: (grid, 1, 1),
4815 block_dim: (BLOCK_SIZE, 1, 1),
4816 shared_mem_bytes: 0,
4817 },
4818 &mut params,
4819 )
4820 .map_err(|e| {
4821 XlogError::Kernel(format!("{ctx}: materialize launch failed: {e}"))
4822 })?;
4823 }
4824 }
4825 Ok(())
4826 };
4827 let rec_mat = unsafe { rec_mat.enqueue_prepared_with(&cu_stream, enqueue) }
4829 .map_err(|error| error.into_xlog_error())?;
4830
4831 rec_mat
4832 .commit()
4833 .map_err(|e| XlogError::Kernel(format!("{ctx}: materialize commit failed: {e}")))?;
4834 cu_stream.synchronize().map_err(|e| {
4835 XlogError::Kernel(format!("{ctx}: materialize stream sync failed: {e}"))
4836 })?;
4837
4838 Ok(CudaBuffer::from_columns_with_host_count(
4839 vec![out_w.into(), out_x.into(), out_y.into(), out_z.into()],
4840 total_rows as u64,
4841 total_rows_device,
4842 out_schema,
4843 total_rows,
4844 ))
4845 }
4846
4847 fn validate_metadata_column(
4848 &self,
4849 input: &CudaBuffer,
4850 key_col_idx: usize,
4851 width: MetadataWidth,
4852 ) -> Result<()> {
4853 if key_col_idx >= input.arity() {
4854 return Err(XlogError::Kernel(format!(
4855 "wcoj_build_metadata: key column {} out of range for arity {}",
4856 key_col_idx,
4857 input.arity()
4858 )));
4859 }
4860 let ty = input.schema().column_type(key_col_idx).ok_or_else(|| {
4861 XlogError::Kernel(format!(
4862 "wcoj_build_metadata: column {} type missing",
4863 key_col_idx
4864 ))
4865 })?;
4866 match (width, ty) {
4867 (MetadataWidth::U32, ScalarType::U32 | ScalarType::Symbol)
4868 | (MetadataWidth::U64, ScalarType::U64) => Ok(()),
4869 (MetadataWidth::U32, other) => Err(XlogError::Kernel(format!(
4870 "wcoj_build_metadata_u32_recorded: column {} must be U32 or Symbol, got {:?}",
4871 key_col_idx, other
4872 ))),
4873 (MetadataWidth::U64, other) => Err(XlogError::Kernel(format!(
4874 "wcoj_build_metadata_u64_recorded: column {} must be U64, got {:?}",
4875 key_col_idx, other
4876 ))),
4877 }
4878 }
4879
4880 fn build_metadata_u32_from_column(
4881 &self,
4882 input: &CudaBuffer,
4883 key_col_idx: usize,
4884 keys: &DeviceMemoryView<u32>,
4885 launch_stream: StreamId,
4886 ) -> Result<WcojRelationMetadata<u32>> {
4887 let n = self.metadata_logical_rows(input)?;
4888 if n == 0 {
4889 return Ok(WcojRelationMetadata {
4890 unique_keys: self.memory().alloc::<u32>(0)?,
4891 fan_out: self.memory().alloc::<u32>(0)?,
4892 prefix_sum: self.memory().alloc::<u32>(0)?,
4893 per_candidate_root: BTreeMap::new(),
4894 total: 0,
4895 key_count: 0,
4896 row_count: 0,
4897 });
4898 }
4899 let mut boundary_mask = self.memory().alloc::<u32>(n as usize)?;
4900 let mut boundary_prefix = self.memory().alloc::<u32>(n as usize)?;
4901 self.mark_metadata_boundaries_u32(
4902 input,
4903 key_col_idx,
4904 keys,
4905 n,
4906 &mut boundary_mask,
4907 &mut boundary_prefix,
4908 launch_stream,
4909 )?;
4910 let key_count = self.metadata_scanned_count(&boundary_mask, &boundary_prefix, n)?;
4911
4912 let mut unique_keys = self.memory().alloc::<u32>(key_count as usize)?;
4913 let mut fan_out = self.memory().alloc::<u32>(key_count as usize)?;
4914 let mut prefix_sum = self.memory().alloc::<u32>(key_count as usize)?;
4915 self.scatter_metadata_u32(
4916 input,
4917 key_col_idx,
4918 keys,
4919 n,
4920 &boundary_mask,
4921 &boundary_prefix,
4922 &mut unique_keys,
4923 &mut fan_out,
4924 &mut prefix_sum,
4925 launch_stream,
4926 )?;
4927
4928 Ok(WcojRelationMetadata {
4929 unique_keys,
4930 fan_out,
4931 prefix_sum,
4932 per_candidate_root: BTreeMap::new(),
4933 total: u64::from(n),
4934 key_count,
4935 row_count: n,
4936 })
4937 }
4938
4939 fn build_metadata_u64_from_column(
4940 &self,
4941 input: &CudaBuffer,
4942 key_col_idx: usize,
4943 keys: &DeviceMemoryView<u64>,
4944 launch_stream: StreamId,
4945 ) -> Result<WcojRelationMetadata<u64>> {
4946 let n = self.metadata_logical_rows(input)?;
4947 if n == 0 {
4948 return Ok(WcojRelationMetadata {
4949 unique_keys: self.memory().alloc::<u64>(0)?,
4950 fan_out: self.memory().alloc::<u32>(0)?,
4951 prefix_sum: self.memory().alloc::<u32>(0)?,
4952 per_candidate_root: BTreeMap::new(),
4953 total: 0,
4954 key_count: 0,
4955 row_count: 0,
4956 });
4957 }
4958 let mut boundary_mask = self.memory().alloc::<u32>(n as usize)?;
4959 let mut boundary_prefix = self.memory().alloc::<u32>(n as usize)?;
4960 self.mark_metadata_boundaries_u64(
4961 input,
4962 key_col_idx,
4963 keys,
4964 n,
4965 &mut boundary_mask,
4966 &mut boundary_prefix,
4967 launch_stream,
4968 )?;
4969 let key_count = self.metadata_scanned_count(&boundary_mask, &boundary_prefix, n)?;
4970
4971 let mut unique_keys = self.memory().alloc::<u64>(key_count as usize)?;
4972 let mut fan_out = self.memory().alloc::<u32>(key_count as usize)?;
4973 let mut prefix_sum = self.memory().alloc::<u32>(key_count as usize)?;
4974 self.scatter_metadata_u64(
4975 input,
4976 key_col_idx,
4977 keys,
4978 n,
4979 &boundary_mask,
4980 &boundary_prefix,
4981 &mut unique_keys,
4982 &mut fan_out,
4983 &mut prefix_sum,
4984 launch_stream,
4985 )?;
4986
4987 Ok(WcojRelationMetadata {
4988 unique_keys,
4989 fan_out,
4990 prefix_sum,
4991 per_candidate_root: BTreeMap::new(),
4992 total: u64::from(n),
4993 key_count,
4994 row_count: n,
4995 })
4996 }
4997
4998 #[expect(
4999 clippy::too_many_arguments,
5000 reason = "metadata boundary marking mirrors the fixed u32 kernel ABI and its scratch/output owners"
5001 )]
5002 fn mark_metadata_boundaries_u32(
5003 &self,
5004 input: &CudaBuffer,
5005 key_col_idx: usize,
5006 keys: &DeviceMemoryView<u32>,
5007 n: u32,
5008 boundary_mask: &mut TrackedCudaSlice<u32>,
5009 boundary_prefix: &mut TrackedCudaSlice<u32>,
5010 launch_stream: StreamId,
5011 ) -> Result<()> {
5012 let runtime = self.memory().runtime().ok_or_else(|| {
5013 XlogError::Kernel(
5014 "wcoj_build_metadata_u32_recorded requires a runtime-backed GpuMemoryManager"
5015 .to_string(),
5016 )
5017 })?;
5018 let cu_stream = runtime
5019 .stream_pool()
5020 .resolve(launch_stream)
5021 .ok_or_else(|| {
5022 XlogError::Kernel(format!(
5023 "wcoj_build_metadata_u32_recorded: launch_stream StreamId({}) does not resolve",
5024 launch_stream.0
5025 ))
5026 })?;
5027 let mut scan_scratch = self.multiblock_scan_u32_scratch_for_len(n)?;
5028 let mut rec = LaunchRecorder::new_strict(launch_stream);
5029 rec.read(input.num_rows_device());
5030 rec.read_column(input.column(key_col_idx).expect("metadata key column"));
5031 rec.write(boundary_mask);
5032 rec.write(boundary_prefix);
5033 for level in scan_scratch.levels() {
5034 rec.read_write(level);
5035 }
5036 rec.preflight(runtime).map_err(|e| {
5037 XlogError::Kernel(format!(
5038 "wcoj_build_metadata_u32_recorded: mark preflight failed: {e}"
5039 ))
5040 })?;
5041 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
5042 let kernel = self
5043 .device()
5044 .inner()
5045 .get_func(
5046 WCOJ_MODULE,
5047 wcoj_kernels::WCOJ_BUILD_METADATA_MARK_BOUNDARIES_U32,
5048 )
5049 .ok_or_else(|| {
5050 XlogError::Kernel(
5051 "wcoj_build_metadata_mark_boundaries_u32 kernel not found".to_string(),
5052 )
5053 })?;
5054 let grid = n.div_ceil(BLOCK_SIZE);
5055 unsafe {
5056 kernel
5057 .clone()
5058 .launch_in(
5059 stream,
5060 LaunchConfig {
5061 grid_dim: (grid, 1, 1),
5062 block_dim: (BLOCK_SIZE, 1, 1),
5063 shared_mem_bytes: 0,
5064 },
5065 (keys, n, &mut *boundary_mask, &mut *boundary_prefix),
5066 )
5067 .map_err(|e| {
5068 XlogError::Kernel(format!(
5069 "wcoj_build_metadata_mark_boundaries_u32 launch failed: {e}"
5070 ))
5071 })?;
5072 }
5073 self.multiblock_scan_u32_inplace_on_stream(
5074 boundary_prefix,
5075 n,
5076 stream,
5077 &mut scan_scratch,
5078 )?;
5079 Ok(())
5080 };
5081 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
5083 .map_err(|error| error.into_xlog_error())?;
5084
5085 rec.commit().map_err(|e| {
5086 XlogError::Kernel(format!(
5087 "wcoj_build_metadata_u32_recorded: mark commit failed: {e}"
5088 ))
5089 })?;
5090 cu_stream.synchronize().map_err(|e| {
5091 XlogError::Kernel(format!(
5092 "wcoj_build_metadata_u32_recorded: mark stream sync failed: {e}"
5093 ))
5094 })?;
5095 Ok(())
5096 }
5097
5098 #[expect(
5099 clippy::too_many_arguments,
5100 reason = "metadata boundary marking mirrors the fixed u64 kernel ABI and its scratch/output owners"
5101 )]
5102 fn mark_metadata_boundaries_u64(
5103 &self,
5104 input: &CudaBuffer,
5105 key_col_idx: usize,
5106 keys: &DeviceMemoryView<u64>,
5107 n: u32,
5108 boundary_mask: &mut TrackedCudaSlice<u32>,
5109 boundary_prefix: &mut TrackedCudaSlice<u32>,
5110 launch_stream: StreamId,
5111 ) -> Result<()> {
5112 let runtime = self.memory().runtime().ok_or_else(|| {
5113 XlogError::Kernel(
5114 "wcoj_build_metadata_u64_recorded requires a runtime-backed GpuMemoryManager"
5115 .to_string(),
5116 )
5117 })?;
5118 let cu_stream = runtime
5119 .stream_pool()
5120 .resolve(launch_stream)
5121 .ok_or_else(|| {
5122 XlogError::Kernel(format!(
5123 "wcoj_build_metadata_u64_recorded: launch_stream StreamId({}) does not resolve",
5124 launch_stream.0
5125 ))
5126 })?;
5127 let mut scan_scratch = self.multiblock_scan_u32_scratch_for_len(n)?;
5128 let mut rec = LaunchRecorder::new_strict(launch_stream);
5129 rec.read(input.num_rows_device());
5130 rec.read_column(input.column(key_col_idx).expect("metadata key column"));
5131 rec.write(boundary_mask);
5132 rec.write(boundary_prefix);
5133 for level in scan_scratch.levels() {
5134 rec.read_write(level);
5135 }
5136 rec.preflight(runtime).map_err(|e| {
5137 XlogError::Kernel(format!(
5138 "wcoj_build_metadata_u64_recorded: mark preflight failed: {e}"
5139 ))
5140 })?;
5141 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
5142 let kernel = self
5143 .device()
5144 .inner()
5145 .get_func(
5146 WCOJ_MODULE,
5147 wcoj_kernels::WCOJ_BUILD_METADATA_MARK_BOUNDARIES_U64,
5148 )
5149 .ok_or_else(|| {
5150 XlogError::Kernel(
5151 "wcoj_build_metadata_mark_boundaries_u64 kernel not found".to_string(),
5152 )
5153 })?;
5154 let grid = n.div_ceil(BLOCK_SIZE);
5155 unsafe {
5156 kernel
5157 .clone()
5158 .launch_in(
5159 stream,
5160 LaunchConfig {
5161 grid_dim: (grid, 1, 1),
5162 block_dim: (BLOCK_SIZE, 1, 1),
5163 shared_mem_bytes: 0,
5164 },
5165 (keys, n, &mut *boundary_mask, &mut *boundary_prefix),
5166 )
5167 .map_err(|e| {
5168 XlogError::Kernel(format!(
5169 "wcoj_build_metadata_mark_boundaries_u64 launch failed: {e}"
5170 ))
5171 })?;
5172 }
5173 self.multiblock_scan_u32_inplace_on_stream(
5174 boundary_prefix,
5175 n,
5176 stream,
5177 &mut scan_scratch,
5178 )?;
5179 Ok(())
5180 };
5181 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
5183 .map_err(|error| error.into_xlog_error())?;
5184
5185 rec.commit().map_err(|e| {
5186 XlogError::Kernel(format!(
5187 "wcoj_build_metadata_u64_recorded: mark commit failed: {e}"
5188 ))
5189 })?;
5190 cu_stream.synchronize().map_err(|e| {
5191 XlogError::Kernel(format!(
5192 "wcoj_build_metadata_u64_recorded: mark stream sync failed: {e}"
5193 ))
5194 })?;
5195 Ok(())
5196 }
5197
5198 #[expect(
5199 clippy::too_many_arguments,
5200 reason = "u32 metadata scattering mirrors the fixed kernel ABI and all device output owners"
5201 )]
5202 fn scatter_metadata_u32(
5203 &self,
5204 input: &CudaBuffer,
5205 key_col_idx: usize,
5206 keys: &DeviceMemoryView<u32>,
5207 n: u32,
5208 boundary_mask: &TrackedCudaSlice<u32>,
5209 boundary_prefix: &TrackedCudaSlice<u32>,
5210 unique_keys: &mut TrackedCudaSlice<u32>,
5211 fan_out: &mut TrackedCudaSlice<u32>,
5212 prefix_sum: &mut TrackedCudaSlice<u32>,
5213 launch_stream: StreamId,
5214 ) -> Result<()> {
5215 let runtime = self.memory().runtime().ok_or_else(|| {
5216 XlogError::Kernel(
5217 "wcoj_build_metadata_u32_recorded requires a runtime-backed GpuMemoryManager"
5218 .to_string(),
5219 )
5220 })?;
5221 let cu_stream = runtime
5222 .stream_pool()
5223 .resolve(launch_stream)
5224 .ok_or_else(|| {
5225 XlogError::Kernel(format!(
5226 "wcoj_build_metadata_u32_recorded: launch_stream StreamId({}) does not resolve",
5227 launch_stream.0
5228 ))
5229 })?;
5230 let mut rec = LaunchRecorder::new_strict(launch_stream);
5231 rec.read(input.num_rows_device());
5232 rec.read_column(input.column(key_col_idx).expect("metadata key column"));
5233 rec.read(boundary_mask);
5234 rec.read(boundary_prefix);
5235 rec.write(unique_keys);
5236 rec.write(fan_out);
5237 rec.write(prefix_sum);
5238 rec.preflight(runtime).map_err(|e| {
5239 XlogError::Kernel(format!(
5240 "wcoj_build_metadata_u32_recorded: scatter preflight failed: {e}"
5241 ))
5242 })?;
5243 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
5244 let kernel = self
5245 .device()
5246 .inner()
5247 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_BUILD_METADATA_SCATTER_U32)
5248 .ok_or_else(|| {
5249 XlogError::Kernel(
5250 "wcoj_build_metadata_scatter_u32 kernel not found".to_string(),
5251 )
5252 })?;
5253 let grid = n.div_ceil(BLOCK_SIZE);
5254 unsafe {
5255 kernel
5256 .clone()
5257 .launch_in(
5258 stream,
5259 LaunchConfig {
5260 grid_dim: (grid, 1, 1),
5261 block_dim: (BLOCK_SIZE, 1, 1),
5262 shared_mem_bytes: 0,
5263 },
5264 (
5265 keys,
5266 n,
5267 boundary_mask,
5268 boundary_prefix,
5269 &mut *unique_keys,
5270 &mut *fan_out,
5271 &mut *prefix_sum,
5272 ),
5273 )
5274 .map_err(|e| {
5275 XlogError::Kernel(format!(
5276 "wcoj_build_metadata_scatter_u32 launch failed: {e}"
5277 ))
5278 })?;
5279 }
5280 Ok(())
5281 };
5282 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
5284 .map_err(|error| error.into_xlog_error())?;
5285
5286 rec.commit().map_err(|e| {
5287 XlogError::Kernel(format!(
5288 "wcoj_build_metadata_u32_recorded: scatter commit failed: {e}"
5289 ))
5290 })?;
5291 cu_stream.synchronize().map_err(|e| {
5292 XlogError::Kernel(format!(
5293 "wcoj_build_metadata_u32_recorded: scatter stream sync failed: {e}"
5294 ))
5295 })?;
5296 Ok(())
5297 }
5298
5299 #[expect(
5300 clippy::too_many_arguments,
5301 reason = "u64 metadata scattering mirrors the fixed kernel ABI and all device output owners"
5302 )]
5303 fn scatter_metadata_u64(
5304 &self,
5305 input: &CudaBuffer,
5306 key_col_idx: usize,
5307 keys: &DeviceMemoryView<u64>,
5308 n: u32,
5309 boundary_mask: &TrackedCudaSlice<u32>,
5310 boundary_prefix: &TrackedCudaSlice<u32>,
5311 unique_keys: &mut TrackedCudaSlice<u64>,
5312 fan_out: &mut TrackedCudaSlice<u32>,
5313 prefix_sum: &mut TrackedCudaSlice<u32>,
5314 launch_stream: StreamId,
5315 ) -> Result<()> {
5316 let runtime = self.memory().runtime().ok_or_else(|| {
5317 XlogError::Kernel(
5318 "wcoj_build_metadata_u64_recorded requires a runtime-backed GpuMemoryManager"
5319 .to_string(),
5320 )
5321 })?;
5322 let cu_stream = runtime
5323 .stream_pool()
5324 .resolve(launch_stream)
5325 .ok_or_else(|| {
5326 XlogError::Kernel(format!(
5327 "wcoj_build_metadata_u64_recorded: launch_stream StreamId({}) does not resolve",
5328 launch_stream.0
5329 ))
5330 })?;
5331 let mut rec = LaunchRecorder::new_strict(launch_stream);
5332 rec.read(input.num_rows_device());
5333 rec.read_column(input.column(key_col_idx).expect("metadata key column"));
5334 rec.read(boundary_mask);
5335 rec.read(boundary_prefix);
5336 rec.write(unique_keys);
5337 rec.write(fan_out);
5338 rec.write(prefix_sum);
5339 rec.preflight(runtime).map_err(|e| {
5340 XlogError::Kernel(format!(
5341 "wcoj_build_metadata_u64_recorded: scatter preflight failed: {e}"
5342 ))
5343 })?;
5344 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
5345 let kernel = self
5346 .device()
5347 .inner()
5348 .get_func(WCOJ_MODULE, wcoj_kernels::WCOJ_BUILD_METADATA_SCATTER_U64)
5349 .ok_or_else(|| {
5350 XlogError::Kernel(
5351 "wcoj_build_metadata_scatter_u64 kernel not found".to_string(),
5352 )
5353 })?;
5354 let grid = n.div_ceil(BLOCK_SIZE);
5355 unsafe {
5356 kernel
5357 .clone()
5358 .launch_in(
5359 stream,
5360 LaunchConfig {
5361 grid_dim: (grid, 1, 1),
5362 block_dim: (BLOCK_SIZE, 1, 1),
5363 shared_mem_bytes: 0,
5364 },
5365 (
5366 keys,
5367 n,
5368 boundary_mask,
5369 boundary_prefix,
5370 &mut *unique_keys,
5371 &mut *fan_out,
5372 &mut *prefix_sum,
5373 ),
5374 )
5375 .map_err(|e| {
5376 XlogError::Kernel(format!(
5377 "wcoj_build_metadata_scatter_u64 launch failed: {e}"
5378 ))
5379 })?;
5380 }
5381 Ok(())
5382 };
5383 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
5385 .map_err(|error| error.into_xlog_error())?;
5386
5387 rec.commit().map_err(|e| {
5388 XlogError::Kernel(format!(
5389 "wcoj_build_metadata_u64_recorded: scatter commit failed: {e}"
5390 ))
5391 })?;
5392 cu_stream.synchronize().map_err(|e| {
5393 XlogError::Kernel(format!(
5394 "wcoj_build_metadata_u64_recorded: scatter stream sync failed: {e}"
5395 ))
5396 })?;
5397 Ok(())
5398 }
5399
5400 fn metadata_scanned_count(
5401 &self,
5402 boundary_mask: &TrackedCudaSlice<u32>,
5403 boundary_prefix: &TrackedCudaSlice<u32>,
5404 n: u32,
5405 ) -> Result<u32> {
5406 let last = n - 1;
5407 let prefix_last = self.dtoh_scalar_untracked::<u32>(boundary_prefix, last as usize)?;
5408 let mask_last = self.dtoh_scalar_untracked::<u32>(boundary_mask, last as usize)?;
5409 Ok(prefix_last + mask_last)
5410 }
5411
5412 fn metadata_logical_rows(&self, input: &CudaBuffer) -> Result<u32> {
5413 if let Some(cached) = input.cached_row_count() {
5414 return Ok(cached);
5415 }
5416 self.dtoh_scalar_untracked::<u32>(input.num_rows_device(), 0)
5417 }
5418}
5419
5420#[derive(Clone, Copy)]
5421enum MetadataWidth {
5422 U32,
5423 U64,
5424}
5425
5426fn metadata_column_u32(input: &CudaBuffer, key_col_idx: usize) -> Result<DeviceMemoryView<u32>> {
5427 let col = input.column(key_col_idx).ok_or_else(|| {
5428 XlogError::Kernel(format!(
5429 "wcoj_build_metadata_u32_recorded: column {} not found",
5430 key_col_idx
5431 ))
5432 })?;
5433 unsafe { col.device_view().cast() }
5436 .ok_or_else(|| XlogError::Kernel("invalid u32 device column layout".into()))
5437}
5438
5439fn metadata_column_u64(input: &CudaBuffer, key_col_idx: usize) -> Result<DeviceMemoryView<u64>> {
5440 let col = input.column(key_col_idx).ok_or_else(|| {
5441 XlogError::Kernel(format!(
5442 "wcoj_build_metadata_u64_recorded: column {} not found",
5443 key_col_idx
5444 ))
5445 })?;
5446 unsafe { col.device_view().cast() }
5449 .ok_or_else(|| XlogError::Kernel("invalid u64 device column layout".into()))
5450}
5451
5452fn validate_binary_u32(ctx: &str, label: &str, input: &CudaBuffer) -> Result<()> {
5453 if input.arity() != 2 {
5454 return Err(XlogError::Kernel(format!(
5455 "{ctx}: {label} must be 2-column, got arity {}",
5456 input.arity()
5457 )));
5458 }
5459 for col_idx in 0..2 {
5460 let ty = input.schema().column_type(col_idx).ok_or_else(|| {
5461 XlogError::Kernel(format!("{ctx}: {label}.col{col_idx} type missing"))
5462 })?;
5463 if !matches!(ty, ScalarType::U32 | ScalarType::Symbol) {
5464 return Err(XlogError::Kernel(format!(
5465 "{ctx}: {label}.col{col_idx} must be U32 or Symbol, got {:?}",
5466 ty
5467 )));
5468 }
5469 }
5470 Ok(())
5471}
5472
5473fn validate_binary_u64(ctx: &str, label: &str, input: &CudaBuffer) -> Result<()> {
5474 if input.arity() != 2 {
5475 return Err(XlogError::Kernel(format!(
5476 "{ctx}: {label} must be 2-column, got arity {}",
5477 input.arity()
5478 )));
5479 }
5480 for col_idx in 0..2 {
5481 let ty = input.schema().column_type(col_idx).ok_or_else(|| {
5482 XlogError::Kernel(format!("{ctx}: {label}.col{col_idx} type missing"))
5483 })?;
5484 if !matches!(ty, ScalarType::U64) {
5485 return Err(XlogError::Kernel(format!(
5486 "{ctx}: {label}.col{col_idx} must be U64, got {:?}",
5487 ty
5488 )));
5489 }
5490 }
5491 Ok(())
5492}
5493
5494fn reinterpret_u8_as_u32(slice: &mut TrackedCudaSlice<u8>) -> Result<DeviceMemoryView<u32>> {
5495 unsafe { slice.device_view().cast() }
5497 .ok_or_else(|| XlogError::Kernel("invalid u32 scratch allocation layout".into()))
5498}
5499
5500fn reinterpret_u8_as_u64(slice: &mut TrackedCudaSlice<u8>) -> Result<DeviceMemoryView<u64>> {
5501 unsafe { slice.device_view().cast() }
5503 .ok_or_else(|| XlogError::Kernel("invalid u64 scratch allocation layout".into()))
5504}