use std::ffi::c_void;
use oxicuda_driver::error::{CudaError, CudaResult};
use oxicuda_driver::loader::try_driver;
use oxicuda_driver::stream::Stream;
use crate::device_buffer::DeviceBuffer;
use crate::host_buffer::PinnedBuffer;
pub fn copy_htod<T: Copy>(dst: &mut DeviceBuffer<T>, src: &[T]) -> CudaResult<()> {
if src.len() != dst.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = dst.byte_size();
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_htod_v2)(
dst.as_device_ptr(),
src.as_ptr().cast::<c_void>(),
byte_size,
)
};
oxicuda_driver::check(rc)
}
pub fn copy_dtoh<T: Copy>(dst: &mut [T], src: &DeviceBuffer<T>) -> CudaResult<()> {
if dst.len() != src.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = src.byte_size();
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_dtoh_v2)(
dst.as_mut_ptr().cast::<c_void>(),
src.as_device_ptr(),
byte_size,
)
};
oxicuda_driver::check(rc)
}
pub fn copy_dtod<T: Copy>(dst: &mut DeviceBuffer<T>, src: &DeviceBuffer<T>) -> CudaResult<()> {
if dst.len() != src.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = src.byte_size();
let api = try_driver()?;
let rc =
unsafe { (api.cu_memcpy_dtod_v2)(dst.as_device_ptr(), src.as_device_ptr(), byte_size) };
oxicuda_driver::check(rc)
}
pub fn copy_htod_async_raw<T: Copy>(
dst: &mut DeviceBuffer<T>,
src: &[T],
stream: &Stream,
) -> CudaResult<()> {
if src.len() != dst.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = dst.byte_size();
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_htod_async_v2)(
dst.as_device_ptr(),
src.as_ptr().cast::<c_void>(),
byte_size,
stream.raw(),
)
};
oxicuda_driver::check(rc)
}
pub fn copy_dtoh_async_raw<T: Copy>(
dst: &mut [T],
src: &DeviceBuffer<T>,
stream: &Stream,
) -> CudaResult<()> {
if dst.len() != src.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = src.byte_size();
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_dtoh_async_v2)(
dst.as_mut_ptr().cast::<c_void>(),
src.as_device_ptr(),
byte_size,
stream.raw(),
)
};
oxicuda_driver::check(rc)
}
pub fn copy_dtod_async<T: Copy>(
dst: &mut DeviceBuffer<T>,
src: &DeviceBuffer<T>,
stream: &Stream,
) -> CudaResult<()> {
if dst.len() != src.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = src.byte_size();
oxicuda_driver::memory_info::memcpy_device_to_device_async(
dst.as_device_ptr(),
src.as_device_ptr(),
byte_size,
stream,
)
}
pub fn copy_htod_async<T: Copy>(
dst: &mut DeviceBuffer<T>,
src: &PinnedBuffer<T>,
stream: &Stream,
) -> CudaResult<()> {
if src.len() != dst.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = dst.byte_size();
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_htod_async_v2)(
dst.as_device_ptr(),
src.as_ptr().cast::<c_void>(),
byte_size,
stream.raw(),
)
};
oxicuda_driver::check(rc)
}
pub fn copy_dtoh_async<T: Copy>(
dst: &mut PinnedBuffer<T>,
src: &DeviceBuffer<T>,
stream: &Stream,
) -> CudaResult<()> {
if dst.len() != src.len() {
return Err(CudaError::InvalidValue);
}
let byte_size = src.byte_size();
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_dtoh_async_v2)(
dst.as_mut_ptr().cast::<c_void>(),
src.as_device_ptr(),
byte_size,
stream.raw(),
)
};
oxicuda_driver::check(rc)
}
pub fn copy_dtoh_region_async<T: Copy>(
dst: &mut PinnedBuffer<T>,
src: &DeviceBuffer<T>,
src_offset: usize,
count: usize,
stream: &Stream,
) -> CudaResult<()> {
let elem_size = std::mem::size_of::<T>();
let src_end = src_offset
.checked_add(count)
.ok_or(CudaError::InvalidValue)?;
if src_end > src.len() || count > dst.len() {
return Err(CudaError::InvalidValue);
}
if count == 0 {
return Ok(());
}
let byte_count = count
.checked_mul(elem_size)
.ok_or(CudaError::InvalidValue)?;
let src_byte_offset = src_offset
.checked_mul(elem_size)
.ok_or(CudaError::InvalidValue)? as u64;
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_dtoh_async_v2)(
dst.as_mut_ptr().cast::<c_void>(),
src.as_device_ptr() + src_byte_offset,
byte_count,
stream.raw(),
)
};
oxicuda_driver::check(rc)
}
pub fn copy_htod_region_async<T: Copy>(
dst: &mut DeviceBuffer<T>,
dst_offset: usize,
src: &PinnedBuffer<T>,
count: usize,
stream: &Stream,
) -> CudaResult<()> {
let elem_size = std::mem::size_of::<T>();
let dst_end = dst_offset
.checked_add(count)
.ok_or(CudaError::InvalidValue)?;
if dst_end > dst.len() || count > src.len() {
return Err(CudaError::InvalidValue);
}
if count == 0 {
return Ok(());
}
let byte_count = count
.checked_mul(elem_size)
.ok_or(CudaError::InvalidValue)?;
let dst_byte_offset = dst_offset
.checked_mul(elem_size)
.ok_or(CudaError::InvalidValue)? as u64;
let api = try_driver()?;
let rc = unsafe {
(api.cu_memcpy_htod_async_v2)(
dst.as_device_ptr() + dst_byte_offset,
src.as_ptr().cast::<c_void>(),
byte_count,
stream.raw(),
)
};
oxicuda_driver::check(rc)
}
#[cfg(test)]
mod tests {
#[test]
fn copy_htod_signature_compiles() {
let _f: fn(&mut super::DeviceBuffer<f32>, &[f32]) -> super::CudaResult<()> =
super::copy_htod;
let _f2: fn(&mut [f32], &super::DeviceBuffer<f32>) -> super::CudaResult<()> =
super::copy_dtoh;
}
#[test]
fn copy_dtod_signature_compiles() {
let _f: fn(
&mut super::DeviceBuffer<f32>,
&super::DeviceBuffer<f32>,
) -> super::CudaResult<()> = super::copy_dtod;
}
#[test]
fn async_raw_htod_signature_compiles() {
let _f: fn(
&mut super::DeviceBuffer<f32>,
&[f32],
&oxicuda_driver::stream::Stream,
) -> super::CudaResult<()> = super::copy_htod_async_raw;
}
#[test]
fn async_raw_dtoh_signature_compiles() {
let _f: fn(
&mut [f32],
&super::DeviceBuffer<f32>,
&oxicuda_driver::stream::Stream,
) -> super::CudaResult<()> = super::copy_dtoh_async_raw;
}
#[test]
fn async_dtod_signature_compiles() {
let _f: fn(
&mut super::DeviceBuffer<f32>,
&super::DeviceBuffer<f32>,
&oxicuda_driver::stream::Stream,
) -> super::CudaResult<()> = super::copy_dtod_async;
}
#[test]
fn async_pinned_htod_signature_compiles() {
let _f: fn(
&mut super::DeviceBuffer<f32>,
&super::PinnedBuffer<f32>,
&oxicuda_driver::stream::Stream,
) -> super::CudaResult<()> = super::copy_htod_async;
}
#[test]
fn region_dtoh_signature_compiles() {
type RegionDtohFn = fn(
&mut super::PinnedBuffer<f32>,
&super::DeviceBuffer<f32>,
usize,
usize,
&oxicuda_driver::stream::Stream,
) -> super::CudaResult<()>;
let _f: RegionDtohFn = super::copy_dtoh_region_async;
}
#[test]
fn region_htod_signature_compiles() {
type RegionHtodFn = fn(
&mut super::DeviceBuffer<f32>,
usize,
&super::PinnedBuffer<f32>,
usize,
&oxicuda_driver::stream::Stream,
) -> super::CudaResult<()>;
let _f: RegionHtodFn = super::copy_htod_region_async;
}
#[cfg(feature = "gpu-tests")]
#[test]
fn copy_dtod_async_round_trips_on_device() {
if oxicuda_driver::init().is_err() {
eprintln!("skipping: CUDA init failed");
return;
}
let Ok(dev) = oxicuda_driver::device::Device::get(0) else {
eprintln!("skipping: no CUDA device");
return;
};
let Ok(ctx) = oxicuda_driver::context::Context::new(&dev) else {
eprintln!("skipping: context creation failed");
return;
};
let ctx = std::sync::Arc::new(ctx);
let Ok(stream) = oxicuda_driver::stream::Stream::new(&ctx) else {
eprintln!("skipping: stream creation failed");
return;
};
let host_src: Vec<f32> = (0..1024).map(|i| i as f32 * 0.5).collect();
let Ok(src) = super::DeviceBuffer::<f32>::from_host(&host_src) else {
eprintln!("skipping: src alloc failed");
return;
};
let Ok(mut dst) = super::DeviceBuffer::<f32>::from_host(&vec![0.0f32; 1024]) else {
eprintln!("skipping: dst alloc failed");
return;
};
match super::copy_dtod_async(&mut dst, &src, &stream) {
Ok(()) => {}
Err(oxicuda_driver::error::CudaError::NotSupported) => {
eprintln!("skipping: driver lacks cuMemcpyDtoDAsync_v2");
return;
}
Err(e) => panic!("copy_dtod_async failed: {e:?}"),
}
stream.synchronize().expect("stream sync failed");
let mut host_out = vec![0.0f32; 1024];
dst.copy_to_host(&mut host_out).expect("copy back failed");
assert_eq!(host_out, host_src);
}
}