1use std::sync::Arc;
4
5use crate::{LaunchAsync, LaunchConfig};
6use xlog_core::{Result, ScalarType, Schema, XlogError};
7
8use super::{d4_kernels, pack_kernels, D4_MODULE, PACK_MODULE};
9use crate::{CudaBuffer, CudaColumn};
10
11fn host_columns_form_canonical_u32_set(schema: &Schema, columns: &[Vec<u8>], rows: usize) -> bool {
12 if rows <= 1 {
13 return true;
14 }
15 if schema.arity() == 0
16 || !(0..schema.arity()).all(|index| {
17 matches!(
18 schema.column_type(index),
19 Some(ScalarType::U32 | ScalarType::Symbol)
20 )
21 })
22 {
23 return false;
24 }
25
26 (1..rows).all(|row| {
27 columns
28 .iter()
29 .map(|column| {
30 let prior = (row - 1) * std::mem::size_of::<u32>();
31 let current = row * std::mem::size_of::<u32>();
32 let prior_value = u32::from_le_bytes(
33 column[prior..prior + 4]
34 .try_into()
35 .expect("validated u32 column"),
36 );
37 let current_value = u32::from_le_bytes(
38 column[current..current + 4]
39 .try_into()
40 .expect("validated u32 column"),
41 );
42 prior_value.cmp(¤t_value)
43 })
44 .find(|ordering| *ordering != std::cmp::Ordering::Equal)
45 == Some(std::cmp::Ordering::Less)
46 })
47}
48
49impl super::CudaKernelProvider {
50 pub fn create_buffer_from_u32_columns(
64 &self,
65 columns: &[&[u32]],
66 schema: Schema,
67 ) -> Result<CudaBuffer> {
68 if columns.is_empty() {
69 return self.create_empty_buffer(schema);
70 }
71
72 let num_rows = columns[0].len();
73 for (i, col) in columns.iter().enumerate() {
74 if col.len() != num_rows {
75 return Err(XlogError::Kernel(format!(
76 "Column {} has {} rows but expected {}",
77 i,
78 col.len(),
79 num_rows
80 )));
81 }
82 }
83
84 let mut cuda_columns = Vec::with_capacity(columns.len());
85 for col_data in columns {
86 let bytes: Vec<u8> = col_data.iter().flat_map(|v| v.to_le_bytes()).collect();
87 let mut col = self.memory.alloc::<u8>(bytes.len())?;
88 self.htod_sync_copy_into_tracked(&bytes, &mut col)
89 .map_err(|e| XlogError::Kernel(format!("Failed to upload column: {}", e)))?;
90 cuda_columns.push(col.into());
91 }
92
93 self.buffer_from_columns(cuda_columns, num_rows as u64, schema)
94 }
95
96 pub fn create_buffer_from_slices(
114 &self,
115 slices: &[&[u8]],
116 schema: Schema,
117 ) -> Result<CudaBuffer> {
118 if slices.len() != schema.arity() {
119 return Err(XlogError::Kernel(format!(
120 "Slice count {} doesn't match schema arity {}",
121 slices.len(),
122 schema.arity()
123 )));
124 }
125
126 if slices.is_empty() {
127 return self.create_empty_buffer(schema);
128 }
129
130 let first_col_size = schema.column_type(0).map(|t| t.size_bytes()).unwrap_or(4);
131 let num_rows = slices[0].len() / first_col_size;
132
133 for (i, slice) in slices.iter().enumerate() {
135 let col_size = schema.column_type(i).map(|t| t.size_bytes()).unwrap_or(4);
136 let col_rows = slice.len() / col_size;
137 if col_rows != num_rows {
138 return Err(XlogError::Kernel(format!(
139 "Column {} has {} rows but expected {} rows (based on first column)",
140 i, col_rows, num_rows
141 )));
142 }
143 if slice.len() % col_size != 0 {
145 return Err(XlogError::Kernel(format!(
146 "Column {} slice length {} is not divisible by type size {}",
147 i,
148 slice.len(),
149 col_size
150 )));
151 }
152 }
153
154 let mut columns = Vec::with_capacity(slices.len());
155
156 for (i, slice) in slices.iter().enumerate() {
157 let mut col = self.memory.alloc::<u8>(slice.len())?;
158 self.htod_sync_copy_into_tracked(slice, &mut col)
159 .map_err(|e| XlogError::Kernel(format!("Failed to upload column {}: {}", i, e)))?;
160 columns.push(col.into());
161 }
162
163 self.buffer_from_columns(columns, num_rows as u64, schema)
164 }
165
166 pub fn create_buffers_from_host_columns(
172 &self,
173 relations: &[(Schema, Vec<Vec<u8>>, usize)],
174 ) -> Result<Vec<CudaBuffer>> {
175 if relations.is_empty() {
176 return Ok(Vec::new());
177 }
178
179 let count_capacity = relations
180 .len()
181 .checked_mul(std::mem::size_of::<u32>())
182 .ok_or_else(|| XlogError::Kernel("Host relation count size overflow".into()))?;
183 let mut payload_bytes = 0usize;
184 let mut spans = Vec::with_capacity(relations.len());
185 let mut count_bytes = Vec::with_capacity(count_capacity);
186 let mut canonical_sets = Vec::with_capacity(relations.len());
187 for (schema, columns, rows) in relations {
188 if columns.len() != schema.arity() {
189 return Err(XlogError::Kernel(format!(
190 "Column count {} doesn't match schema arity {}",
191 columns.len(),
192 schema.arity()
193 )));
194 }
195 let rows_u32 = u32::try_from(*rows)
196 .map_err(|_| XlogError::Kernel(format!("Row count {} exceeds u32::MAX", rows)))?;
197 count_bytes.extend_from_slice(&rows_u32.to_le_bytes());
198
199 let mut column_spans = Vec::with_capacity(columns.len());
200 for (index, column) in columns.iter().enumerate() {
201 let width = schema
202 .column_type(index)
203 .map(|typ| typ.size_bytes())
204 .unwrap_or(4);
205 let expected = rows
206 .checked_mul(width)
207 .ok_or_else(|| XlogError::Kernel("Host column byte length overflow".into()))?;
208 if column.len() != expected {
209 return Err(XlogError::Kernel(format!(
210 "Column {} has {} bytes but expected {} for {} rows",
211 index,
212 column.len(),
213 expected,
214 rows
215 )));
216 }
217 let alignment = width.max(std::mem::align_of::<u64>());
220 let remainder = payload_bytes % alignment;
221 let padding = if remainder == 0 {
222 0
223 } else {
224 alignment - remainder
225 };
226 let start = payload_bytes.checked_add(padding).ok_or_else(|| {
227 XlogError::Kernel("Host relation payload size overflow".into())
228 })?;
229 let end = start.checked_add(column.len()).ok_or_else(|| {
230 XlogError::Kernel("Host relation payload size overflow".into())
231 })?;
232 column_spans.push(start..end);
233 payload_bytes = end;
234 }
235 canonical_sets.push(host_columns_form_canonical_u32_set(schema, columns, *rows));
236 spans.push(column_spans);
237 }
238
239 let mut payload = Vec::with_capacity(payload_bytes);
240 for ((_, columns, _), column_spans) in relations.iter().zip(&spans) {
241 for (column, span) in columns.iter().zip(column_spans) {
242 payload.resize(span.start, 0);
243 payload.extend_from_slice(column);
244 }
245 }
246 let payload_owner = if relations.iter().any(|(_, columns, _)| !columns.is_empty()) {
247 let mut allocation = self.memory.alloc::<u8>(payload.len())?;
248 if !payload.is_empty() {
249 self.htod_sync_copy_into_tracked(&payload, &mut allocation)?;
250 }
251 Some(allocation)
252 } else {
253 None
254 };
255 let mut counts = self.memory.alloc::<u8>(count_bytes.len())?;
256 self.htod_launch_metadata_sync_copy_into(&count_bytes, &mut counts)?;
257
258 let mut buffers = Vec::with_capacity(relations.len());
259 for (index, ((schema, _, rows), column_spans)) in relations.iter().zip(spans).enumerate() {
260 let mut columns = Vec::<CudaColumn>::with_capacity(column_spans.len());
261 for span in column_spans {
262 let column = payload_owner
263 .as_ref()
264 .expect("non-nullary relation has a payload owner")
265 .try_owned_subslice::<u8>(span)
266 .ok_or_else(|| XlogError::Kernel("Host column view is invalid".into()))?;
267 columns.push(column.into());
268 }
269 let start = index * std::mem::size_of::<u32>();
270 let count = counts
271 .try_owned_subslice::<u32>(start..start + std::mem::size_of::<u32>())
272 .ok_or_else(|| XlogError::Kernel("Host row count view is invalid".into()))?;
273 let mut buffer = CudaBuffer::from_columns_with_host_count(
274 columns,
275 *rows as u64,
276 count,
277 schema.clone(),
278 *rows as u32,
279 );
280 if canonical_sets[index] {
281 buffer.certify_canonical_full_row_set();
282 }
283 buffers.push(buffer);
284 }
285 Ok(buffers)
286 }
287
288 pub fn to_arrow_device_record_batch(
295 &self,
296 buffer: CudaBuffer,
297 ) -> Result<crate::arrow_device::ArrowDeviceArrayOwned> {
298 use crate::device_runtime::Access;
299 use crate::memory::{with_memory_access, DeviceRead};
300 use arrow::array::ArrayData;
301 use arrow::datatypes::{DataType, Field};
302 use arrow::ffi::to_ffi;
303
304 use crate::arrow_device::{ArrowDeviceArray, ARROW_DEVICE_CUDA};
305
306 let stream = self.device.inner().stream();
307 let _ordinary = crate::cuda_graph::reserve_uncaptured_stream(stream).map_err(|error| {
308 XlogError::Kernel(format!("Arrow export stream admission: {error}"))
309 })?;
310 let buffer = Arc::new(buffer);
311 let row_cap = buffer.num_rows();
312 let num_rows_u32 = u32::try_from(row_cap).map_err(|_| {
313 XlogError::Kernel(format!(
314 "Arrow device export supports at most {} rows, got {}",
315 u32::MAX,
316 row_cap
317 ))
318 })?;
319
320 let assert_fn = self
322 .device
323 .inner()
324 .get_func(D4_MODULE, d4_kernels::D4_ASSERT_U32_EQ)
325 .ok_or_else(|| XlogError::Kernel("d4_assert_u32_eq kernel not found".to_string()))?;
326 unsafe {
328 assert_fn.clone().launch(
329 LaunchConfig {
330 grid_dim: (1, 1, 1),
331 block_dim: (1, 1, 1),
332 shared_mem_bytes: 0,
333 },
334 (buffer.num_rows_device(), num_rows_u32),
335 )
336 }
337 .map_err(|e| XlogError::Kernel(format!("d4_assert_u32_eq failed: {}", e)))?;
338 let accesses = buffer
342 .columns()
343 .iter()
344 .map(|column| column.device_view().access(Access::Read))
345 .collect::<crate::device_runtime::ResourceResult<Vec<_>>>()
346 .map_err(|error| XlogError::Kernel(error.to_string()))?;
347 with_memory_access(Arc::clone(stream), accesses, |_| Ok(stream.synchronize()?))
348 .map_err(|error| XlogError::Kernel(format!("Arrow producer readiness: {error}")))?;
349
350 let num_rows = usize::try_from(num_rows_u32)
351 .map_err(|_| XlogError::Kernel("Arrow device export row count overflow".to_string()))?;
352
353 let mut fields: Vec<Field> = Vec::with_capacity(buffer.arity());
354 let mut children: Vec<ArrayData> = Vec::with_capacity(buffer.arity());
355
356 for (col_idx, (name, scalar_type)) in buffer.schema().columns.iter().enumerate() {
357 let (field, child) =
358 self.build_arrow_device_child(&buffer, col_idx, name, *scalar_type, num_rows)?;
359 fields.push(field);
360 children.push(child);
361 }
362
363 let struct_type = DataType::Struct(fields.into());
364 let struct_data = ArrayData::builder(struct_type)
365 .len(num_rows)
366 .child_data(children)
367 .build()
368 .map_err(|e| XlogError::Kernel(format!("Arrow device export failed: {}", e)))?;
369
370 let (ffi_array, ffi_schema) =
371 to_ffi(&struct_data).map_err(|e| XlogError::Kernel(format!("{}", e)))?;
372 let array_ptr = Box::into_raw(Box::new(ffi_array));
373 let schema_ptr = Box::into_raw(Box::new(ffi_schema));
374
375 Ok(unsafe {
378 ArrowDeviceArray::new(
379 ARROW_DEVICE_CUDA,
380 self.device.ordinal() as i32,
381 array_ptr,
382 schema_ptr,
383 )
384 })
385 }
386
387 #[cfg(feature = "arrow-device-import")]
391 pub fn from_arrow_device_record_batch(
392 &self,
393 device_array: crate::arrow_device::ArrowDeviceArrayOwned,
394 ) -> Result<CudaBuffer> {
395 use arrow::array::ArrayData;
396 use arrow::datatypes::DataType;
397 use arrow::ffi::from_ffi;
398 use std::sync::Arc;
399
400 use crate::arrow_device::{ArrowDeviceImport, ARROW_DEVICE_CUDA};
401 use crate::memory::CudaColumn;
402
403 let (device_type, device_id, outer_owner, ffi_array, ffi_schema) =
404 unsafe { device_array.into_ffi_parts() };
406
407 if device_type != ARROW_DEVICE_CUDA {
408 return Err(XlogError::Kernel(format!(
409 "Arrow device import requires CUDA device type={}, got {}",
410 ARROW_DEVICE_CUDA, device_type
411 )));
412 }
413 if device_id != self.device.ordinal() as i32 {
414 return Err(XlogError::Kernel(format!(
415 "Arrow device import device id mismatch: expected {}, got {}",
416 self.device.ordinal(),
417 device_id
418 )));
419 }
420
421 let data: ArrayData = unsafe { from_ffi(ffi_array, &ffi_schema) }
423 .map_err(|e| XlogError::Kernel(format!("Arrow device import failed: {}", e)))?;
424 drop(ffi_schema);
425 let keepalive = Arc::new(ArrowDeviceImport::from_ffi(data, outer_owner));
426 let data = keepalive.data();
427
428 let (fields, children) = match data.data_type() {
429 DataType::Struct(fields) => (fields.clone(), data.child_data().to_vec()),
430 other => {
431 return Err(XlogError::Kernel(format!(
432 "Arrow device import expects Struct, got {:?}",
433 other
434 )))
435 }
436 };
437
438 if data.offset() != 0 {
439 return Err(XlogError::Kernel(
440 "Arrow device import does not support non-zero offsets".to_string(),
441 ));
442 }
443 if data.null_count() > 0 || data.nulls().is_some() {
444 return Err(XlogError::Kernel(
445 "Arrow device import does not support nulls".to_string(),
446 ));
447 }
448
449 let num_rows = data.len();
450 if fields.len() != children.len() {
451 return Err(XlogError::Kernel(
452 "Arrow device import field/child length mismatch".to_string(),
453 ));
454 }
455
456 let mut columns = Vec::with_capacity(children.len());
457 let mut schema_cols = Vec::with_capacity(children.len());
458
459 for (field, child) in fields.iter().zip(children.iter()) {
460 if child.len() != num_rows {
461 return Err(XlogError::Kernel(
462 "Arrow device import child length mismatch".to_string(),
463 ));
464 }
465 if child.offset() != 0 {
466 return Err(XlogError::Kernel(
467 "Arrow device import does not support child offsets".to_string(),
468 ));
469 }
470 if child.null_count() > 0 || child.nulls().is_some() {
471 return Err(XlogError::Kernel(
472 "Arrow device import does not support child nulls".to_string(),
473 ));
474 }
475
476 let (scalar_type, elem_size) = Self::scalar_type_from_arrow_field(field)?;
477 let buffers = child.buffers();
478 let buf = buffers.first().ok_or_else(|| {
479 XlogError::Kernel("Arrow device import missing value buffer".to_string())
480 })?;
481 let len_bytes = buf.len();
482 let expected_bytes = num_rows.checked_mul(elem_size).ok_or_else(|| {
483 XlogError::Kernel("Arrow device import size overflow".to_string())
484 })?;
485 if len_bytes != expected_bytes {
486 return Err(XlogError::Kernel(format!(
487 "Arrow device import buffer size mismatch: expected {}, got {}",
488 expected_bytes, len_bytes
489 )));
490 }
491
492 let ptr = buf.as_ptr();
493 if ptr.is_null() && len_bytes > 0 {
494 return Err(XlogError::Kernel(
495 "Arrow device import got null buffer pointer".to_string(),
496 ));
497 }
498 let device_ptr = ptr as usize as cudarc::driver::sys::CUdeviceptr;
499 columns.push(unsafe {
502 CudaColumn::arrow_device(
503 device_ptr,
504 len_bytes,
505 self.device().inner().stream().clone(),
506 keepalive.clone(),
507 )
508 });
509 schema_cols.push((field.name().to_string(), scalar_type));
510 }
511
512 let schema = Schema::new(schema_cols);
513 self.buffer_from_columns(columns, num_rows as u64, schema)
514 }
515
516 pub fn to_arrow_record_batch(
532 &self,
533 buffer: &CudaBuffer,
534 ) -> Result<arrow::record_batch::RecordBatch> {
535 use arrow::array::*;
536 use arrow::datatypes::{Field, Schema as ArrowSchema};
537 use arrow::record_batch::{RecordBatch, RecordBatchOptions};
538
539 let num_rows = self.device_row_count(buffer)?;
540
541 let fields: Vec<Field> = buffer
542 .schema
543 .columns
544 .iter()
545 .map(|(name, scalar_type)| Field::new(name, scalar_type.to_arrow_type(), false))
546 .collect();
547 let arrow_schema = Arc::new(ArrowSchema::new(fields));
548
549 let mut arrays: Vec<Arc<dyn Array>> = Vec::with_capacity(buffer.arity());
550
551 for (col_idx, (_, scalar_type)) in buffer.schema.columns.iter().enumerate() {
552 let col = buffer
553 .column(col_idx)
554 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
555
556 if num_rows == 0 {
558 let array: Arc<dyn Array> = match scalar_type {
559 ScalarType::Bool => Arc::new(BooleanArray::from(Vec::<bool>::new())),
560 ScalarType::U32 => Arc::new(UInt32Array::from(Vec::<u32>::new())),
561 ScalarType::Symbol => Arc::new(xlog_core::symbol::to_arrow(&[])),
562 ScalarType::I32 => Arc::new(Int32Array::from(Vec::<i32>::new())),
563 ScalarType::U64 => Arc::new(UInt64Array::from(Vec::<u64>::new())),
564 ScalarType::I64 => Arc::new(Int64Array::from(Vec::<i64>::new())),
565 ScalarType::F32 => Arc::new(Float32Array::from(Vec::<f32>::new())),
566 ScalarType::F64 => Arc::new(Float64Array::from(Vec::<f64>::new())),
567 };
568 arrays.push(array);
569 continue;
570 }
571
572 let elem_size = scalar_type.size_bytes();
573 let num_bytes = num_rows
574 .checked_mul(elem_size)
575 .ok_or_else(|| XlogError::Kernel("Row byte size overflow".to_string()))?;
576 let mut bytes = vec![0u8; num_bytes];
577 let col_view = self.column_bytes_view(col, num_bytes)?;
578 self.device
579 .inner()
580 .dtoh_sync_copy_into(&col_view, &mut bytes)
581 .map_err(|e| XlogError::Kernel(format!("Failed to download column: {}", e)))?;
582
583 let array: Arc<dyn Array> = match scalar_type {
584 ScalarType::Bool => Arc::new(BooleanArray::from(
585 bytes.iter().map(|&b| b != 0).collect::<Vec<_>>(),
586 )),
587 ScalarType::U32 => {
588 let values: Vec<u32> = bytes
589 .as_chunks::<4>()
590 .0
591 .iter()
592 .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
593 .collect();
594 Arc::new(UInt32Array::from(values))
595 }
596 ScalarType::Symbol => {
597 let values: Vec<u32> = bytes
598 .as_chunks::<4>()
599 .0
600 .iter()
601 .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
602 .collect();
603 Arc::new(xlog_core::symbol::to_arrow(&values))
604 }
605 ScalarType::I32 => {
606 let values: Vec<i32> = bytes
607 .as_chunks::<4>()
608 .0
609 .iter()
610 .map(|c| i32::from_le_bytes([c[0], c[1], c[2], c[3]]))
611 .collect();
612 Arc::new(Int32Array::from(values))
613 }
614 ScalarType::U64 => {
615 let values: Vec<u64> = bytes
616 .as_chunks::<8>()
617 .0
618 .iter()
619 .map(|c| {
620 u64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]])
621 })
622 .collect();
623 Arc::new(UInt64Array::from(values))
624 }
625 ScalarType::I64 => {
626 let values: Vec<i64> = bytes
627 .as_chunks::<8>()
628 .0
629 .iter()
630 .map(|c| {
631 i64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]])
632 })
633 .collect();
634 Arc::new(Int64Array::from(values))
635 }
636 ScalarType::F32 => {
637 let values: Vec<f32> = bytes
638 .as_chunks::<4>()
639 .0
640 .iter()
641 .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
642 .collect();
643 Arc::new(Float32Array::from(values))
644 }
645 ScalarType::F64 => {
646 let values: Vec<f64> = bytes
647 .as_chunks::<8>()
648 .0
649 .iter()
650 .map(|c| {
651 f64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]])
652 })
653 .collect();
654 Arc::new(Float64Array::from(values))
655 }
656 };
657
658 arrays.push(array);
659 }
660
661 let options = RecordBatchOptions::new().with_row_count(Some(num_rows));
662 RecordBatch::try_new_with_options(arrow_schema, arrays, &options)
663 .map_err(|e| XlogError::Kernel(format!("Failed to create RecordBatch: {}", e)))
664 }
665
666 fn build_arrow_device_child(
667 &self,
668 buffer: &Arc<CudaBuffer>,
669 col_idx: usize,
670 name: &str,
671 scalar_type: ScalarType,
672 num_rows: usize,
673 ) -> Result<(arrow::datatypes::Field, arrow::array::ArrayData)> {
674 use arrow::array::ArrayData;
675 use arrow::buffer::Buffer;
676 use arrow::datatypes::{DataType, Field};
677 use std::collections::HashMap;
678 use std::ptr::NonNull;
679
680 use crate::arrow_device::ArrowCudaAllocation;
681
682 let col = buffer
683 .column(col_idx)
684 .ok_or_else(|| XlogError::Kernel(format!("Column {} not found", col_idx)))?;
685
686 let (dtype, metadata) = if scalar_type == ScalarType::Symbol {
687 let mut meta = HashMap::new();
688 meta.insert("xlog.symbol".to_string(), "true".to_string());
689 meta.insert("xlog.symbol_encoding".to_string(), "u32".to_string());
690 (DataType::UInt32, Some(meta))
691 } else {
692 (scalar_type.to_arrow_type(), None)
693 };
694
695 let field = match metadata {
696 Some(meta) => Field::new(name, dtype.clone(), false).with_metadata(meta),
697 None => Field::new(name, dtype.clone(), false),
698 };
699
700 let elem_size = match scalar_type {
701 ScalarType::Symbol => 4usize,
702 _ => scalar_type.size_bytes(),
703 };
704
705 let len_bytes = num_rows
706 .checked_mul(elem_size)
707 .ok_or_else(|| XlogError::Kernel("Arrow device export size overflow".to_string()))?;
708
709 let mut extra = Vec::new();
710 let (ptr, len) = match scalar_type {
711 ScalarType::Bool => {
712 let packed_len = num_rows.div_ceil(8);
713 let mut packed = self.memory.alloc::<u8>(packed_len)?;
714 let pack_fn = self
715 .device
716 .inner()
717 .get_func(PACK_MODULE, pack_kernels::PACK_BOOLS_TO_BITMAP)
718 .ok_or_else(|| {
719 XlogError::Kernel("pack_bools_to_bitmap kernel not found".to_string())
720 })?;
721 let block_size = 256u32;
722 let grid_size = (packed_len as u32).div_ceil(block_size);
723 unsafe {
725 pack_fn.clone().launch(
726 LaunchConfig {
727 grid_dim: (grid_size, 1, 1),
728 block_dim: (block_size, 1, 1),
729 shared_mem_bytes: 0,
730 },
731 (col, num_rows as u32, &mut packed),
732 )
733 }
734 .map_err(|e| XlogError::Kernel(format!("pack_bools_to_bitmap failed: {}", e)))?;
735 self.device.synchronize()?;
736 let ptr = *packed.device_ptr() as usize as *mut u8;
737 extra.push(packed);
738 (ptr, packed_len)
739 }
740 _ => {
741 let ptr = *col.device_ptr() as usize as *mut u8;
742 (ptr, len_bytes)
743 }
744 };
745
746 let alloc = Arc::new(ArrowCudaAllocation::new(Arc::clone(buffer), extra));
747 let nn = if len == 0 {
748 NonNull::dangling()
749 } else {
750 NonNull::new(ptr).ok_or_else(|| {
751 XlogError::Kernel("Arrow device export got null device pointer".to_string())
752 })?
753 };
754 let buf = unsafe { Buffer::from_custom_allocation(nn, len, alloc) };
756
757 let data = ArrayData::builder(dtype)
758 .len(num_rows)
759 .add_buffer(buf)
760 .build()
761 .map_err(|e| XlogError::Kernel(format!("Arrow device export failed: {}", e)))?;
762
763 Ok((field, data))
764 }
765
766 #[cfg(feature = "arrow-device-import")]
767 fn scalar_type_from_arrow_field(
768 field: &arrow::datatypes::Field,
769 ) -> Result<(ScalarType, usize)> {
770 use arrow::datatypes::DataType;
771
772 let is_symbol = field
774 .metadata()
775 .get("xlog.symbol")
776 .map(|v| v == "true")
777 .unwrap_or(false);
778
779 let scalar = match field.data_type() {
780 DataType::Boolean => {
781 return Err(XlogError::Kernel(
782 "Arrow device import does not support bit-packed bool yet".to_string(),
783 ))
784 }
785 DataType::UInt32 if is_symbol => ScalarType::Symbol,
786 dt => ScalarType::from_arrow_type(dt).ok_or_else(|| {
787 XlogError::Kernel(format!("Arrow device import unsupported type {:?}", dt))
788 })?,
789 };
790
791 let elem_size = match scalar {
792 ScalarType::Symbol => 4usize,
793 _ => scalar.size_bytes(),
794 };
795
796 Ok((scalar, elem_size))
797 }
798
799 pub fn from_arrow_record_batch(
812 &self,
813 record_batch: &arrow::record_batch::RecordBatch,
814 ) -> Result<CudaBuffer> {
815 use arrow::array::*;
816 use arrow::datatypes::DataType;
817
818 let num_rows = record_batch.num_rows() as u64;
819
820 if num_rows == 0 {
821 let columns: Vec<(String, ScalarType)> = record_batch
822 .schema()
823 .fields()
824 .iter()
825 .filter_map(|f| {
826 ScalarType::from_arrow_type(f.data_type()).map(|st| (f.name().clone(), st))
827 })
828 .collect();
829 return self.create_empty_buffer(Schema::new(columns));
830 }
831
832 let mut columns = Vec::with_capacity(record_batch.num_columns());
833 let mut schema_cols = Vec::with_capacity(record_batch.num_columns());
834
835 for (col_idx, field) in record_batch.schema().fields().iter().enumerate() {
836 let array = record_batch.column(col_idx);
837 let scalar_type = ScalarType::from_arrow_type(field.data_type()).ok_or_else(|| {
838 XlogError::Kernel(format!("Unsupported Arrow type: {:?}", field.data_type()))
839 })?;
840
841 let bytes: Vec<u8> = match field.data_type() {
842 DataType::Boolean => {
843 let arr = array.as_any().downcast_ref::<BooleanArray>().unwrap();
844 arr.iter()
845 .map(|v| if v.unwrap_or(false) { 1u8 } else { 0u8 })
846 .collect()
847 }
848 DataType::UInt32 => {
849 let arr = array.as_any().downcast_ref::<UInt32Array>().unwrap();
850 arr.values().iter().flat_map(|v| v.to_le_bytes()).collect()
851 }
852 DataType::Int32 => {
853 let arr = array.as_any().downcast_ref::<Int32Array>().unwrap();
854 arr.values().iter().flat_map(|v| v.to_le_bytes()).collect()
855 }
856 DataType::UInt64 => {
857 let arr = array.as_any().downcast_ref::<UInt64Array>().unwrap();
858 arr.values().iter().flat_map(|v| v.to_le_bytes()).collect()
859 }
860 DataType::Int64 => {
861 let arr = array.as_any().downcast_ref::<Int64Array>().unwrap();
862 arr.values().iter().flat_map(|v| v.to_le_bytes()).collect()
863 }
864 DataType::Float32 => {
865 let arr = array.as_any().downcast_ref::<Float32Array>().unwrap();
866 arr.values().iter().flat_map(|v| v.to_le_bytes()).collect()
867 }
868 DataType::Float64 => {
869 let arr = array.as_any().downcast_ref::<Float64Array>().unwrap();
870 arr.values().iter().flat_map(|v| v.to_le_bytes()).collect()
871 }
872 _ => {
873 return Err(XlogError::Kernel(format!(
874 "Unsupported Arrow type: {:?}",
875 field.data_type()
876 )))
877 }
878 };
879
880 let mut d_col = self.memory.alloc::<u8>(bytes.len())?;
881 self.htod_sync_copy_into_tracked(&bytes, &mut d_col)
882 .map_err(|e| XlogError::Kernel(format!("Failed to upload column: {}", e)))?;
883
884 columns.push(d_col.into());
885 schema_cols.push((field.name().clone(), scalar_type));
886 }
887
888 self.buffer_from_columns(columns, num_rows, Schema::new(schema_cols))
889 }
890
891 pub fn to_arrow_ipc_stream(&self, buffer: &CudaBuffer) -> Result<Vec<u8>> {
898 use arrow::ipc::writer::StreamWriter;
899
900 let batch = self.to_arrow_record_batch(buffer)?;
901 let mut out = Vec::new();
902 let mut writer = StreamWriter::try_new(&mut out, &batch.schema())
903 .map_err(|e| XlogError::Kernel(format!("Failed to create Arrow IPC writer: {}", e)))?;
904 writer
905 .write(&batch)
906 .map_err(|e| XlogError::Kernel(format!("Failed to write Arrow RecordBatch: {}", e)))?;
907 writer
908 .finish()
909 .map_err(|e| XlogError::Kernel(format!("Failed to finish Arrow IPC stream: {}", e)))?;
910 Ok(out)
911 }
912
913 pub fn from_arrow_ipc_stream(&self, ipc: &[u8]) -> Result<CudaBuffer> {
917 use arrow::ipc::reader::StreamReader;
918 use std::io::Cursor;
919
920 let cursor = Cursor::new(ipc);
921 let mut reader = StreamReader::try_new(cursor, None)
922 .map_err(|e| XlogError::Kernel(format!("Failed to create Arrow IPC reader: {}", e)))?;
923
924 let batches = reader.by_ref();
925 let first = batches
926 .next()
927 .ok_or_else(|| {
928 XlogError::Kernel("Arrow IPC stream contained no record batches".to_string())
929 })?
930 .map_err(|e| XlogError::Kernel(format!("Failed to read Arrow RecordBatch: {}", e)))?;
931
932 if batches.next().is_some() {
933 return Err(XlogError::Kernel(
934 "Arrow IPC stream contains multiple record batches; this API expects exactly one"
935 .to_string(),
936 ));
937 }
938
939 self.from_arrow_record_batch(&first)
940 }
941
942 pub fn write_arrow_ipc_stream_file<P: AsRef<std::path::Path>>(
944 &self,
945 buffer: &CudaBuffer,
946 path: P,
947 ) -> Result<()> {
948 let bytes = self.to_arrow_ipc_stream(buffer)?;
949 std::fs::write(&path, bytes).map_err(|e| {
950 XlogError::Kernel(format!(
951 "Failed to write Arrow IPC stream to {}: {}",
952 path.as_ref().display(),
953 e
954 ))
955 })?;
956 Ok(())
957 }
958
959 pub fn read_arrow_ipc_stream_file<P: AsRef<std::path::Path>>(
961 &self,
962 path: P,
963 ) -> Result<CudaBuffer> {
964 let bytes = std::fs::read(&path).map_err(|e| {
965 XlogError::Kernel(format!(
966 "Failed to read Arrow IPC stream from {}: {}",
967 path.as_ref().display(),
968 e
969 ))
970 })?;
971 self.from_arrow_ipc_stream(&bytes)
972 }
973}