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