mod handle;
mod transfer;
use std::{ffi::c_void, ptr::NonNull, sync::Arc};
use cudarc::driver::{CudaContext, CudaEvent, CudaStream, result, sys};
use self::handle::MemoryPoolHandle;
pub use self::handle::MemoryPoolStats;
use super::driver::{Context, Stream};
use crate::Result;
#[derive(Clone, Debug)]
pub struct MemoryPool {
handle: MemoryPoolHandle,
context: Arc<CudaContext>,
}
impl Context {
pub fn default_memory_pool(&self) -> Result<MemoryPool> {
self.inner.bind_to_thread()?;
let handle = unsafe { result::device::get_default_mem_pool(self.inner.cu_device()) }?;
Ok(MemoryPool {
handle: MemoryPoolHandle(handle),
context: self.inner.clone(),
})
}
pub fn allocate_pinned(&self, bytes: usize) -> Result<PinnedBuffer> {
self.inner.bind_to_thread()?;
let raw = unsafe { result::malloc_host(bytes, 0) }?;
let pointer = NonNull::new(raw.cast::<u8>()).ok_or(crate::Error::NullAllocation)?;
unsafe { pointer.as_ptr().write_bytes(0, bytes) };
let event = self.inner.new_event(Some(sys::CUevent_flags::CU_EVENT_DISABLE_TIMING))?;
Ok(PinnedBuffer { pointer, bytes, event })
}
}
impl MemoryPool {
pub fn set_release_threshold(&self, bytes: u64) -> Result<()> {
self.context.bind_to_thread()?;
set_attribute(
self.handle.0,
sys::CUmemPool_attribute::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD,
bytes,
)
}
pub fn stats(&self) -> Result<MemoryPoolStats> {
self.context.bind_to_thread()?;
Ok(MemoryPoolStats {
reserved: get_attribute(
self.handle.0,
sys::CUmemPool_attribute::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT,
)?,
used: get_attribute(
self.handle.0,
sys::CUmemPool_attribute::CU_MEMPOOL_ATTR_USED_MEM_CURRENT,
)?,
})
}
pub fn trim_to(&self, bytes: usize) -> Result<()> {
self.context.bind_to_thread()?;
Ok(unsafe { result::mem_pool::trim_to(self.handle.0, bytes) }?)
}
pub fn allocate(&self, stream: &Stream, bytes: usize, zeroed: bool) -> Result<DeviceBuffer> {
if !Arc::ptr_eq(&self.context, stream.inner.context()) {
return Err(crate::Error::ContextMismatch);
}
self.context.bind_to_thread()?;
let pointer = unsafe {
result::mem_pool::alloc_async(self.handle.0, bytes, stream.inner.cu_stream())
}?;
if zeroed {
unsafe { result::memset_d8_async(pointer, 0, bytes, stream.inner.cu_stream()) }?;
}
Ok(DeviceBuffer {
pointer,
bytes,
stream: stream.inner.clone(),
})
}
}
#[derive(Debug)]
pub struct DeviceBuffer {
pointer: sys::CUdeviceptr,
bytes: usize,
stream: Arc<CudaStream>,
}
impl DeviceBuffer {
#[must_use]
pub const fn bytes(&self) -> usize {
self.bytes
}
#[must_use]
pub fn argument(&self) -> super::compiler::KernelArgument {
super::compiler::KernelArgument::Pointer {
value: self.pointer,
stream: self.stream.cu_stream(),
}
}
#[must_use]
pub(super) const fn pointer(&self) -> sys::CUdeviceptr {
self.pointer
}
}
impl Drop for DeviceBuffer {
fn drop(&mut self) {
self.stream.context().record_err(self.stream.context().bind_to_thread());
self.stream
.context()
.record_err(unsafe { result::free_async(self.pointer, self.stream.cu_stream()) });
}
}
#[derive(Debug)]
pub struct PinnedBuffer {
pointer: NonNull<u8>,
bytes: usize,
event: CudaEvent,
}
unsafe impl Send for PinnedBuffer {}
impl PinnedBuffer {
#[must_use]
pub const fn bytes(&self) -> usize {
self.bytes
}
pub fn with_slice<T, R>(&self, len: usize, read: impl FnOnce(&[T]) -> R) -> Result<R> {
validate_view::<T>(len, self.bytes)?;
self.event.synchronize()?;
let values = unsafe { std::slice::from_raw_parts(self.pointer.as_ptr().cast(), len) };
Ok(read(values))
}
pub fn with_mut_slice<T, R>(
&mut self,
len: usize,
write: impl FnOnce(&mut [T]) -> R,
) -> Result<R> {
validate_view::<T>(len, self.bytes)?;
self.event.synchronize()?;
let values = unsafe { std::slice::from_raw_parts_mut(self.pointer.as_ptr().cast(), len) };
Ok(write(values))
}
fn record(&self, stream: &Stream) -> Result<()> {
Ok(self.event.record(&stream.inner)?)
}
}
impl Drop for PinnedBuffer {
fn drop(&mut self) {
self.event.context().record_err(self.event.synchronize());
self.event
.context()
.record_err(unsafe { result::free_host(self.pointer.as_ptr().cast::<c_void>()) });
}
}
pub(super) fn ensure_stream(buffer: &DeviceBuffer, stream: &Stream) -> Result<()> {
if Arc::ptr_eq(&buffer.stream, &stream.inner) {
Ok(())
} else {
Err(crate::Error::StreamMismatch)
}
}
fn get_attribute(pool: sys::CUmemoryPool, attribute: sys::CUmemPool_attribute) -> Result<u64> {
let mut value = 0_u64;
unsafe { result::mem_pool::get_attribute(pool, attribute, (&raw mut value).cast()) }?;
Ok(value)
}
fn set_attribute(
pool: sys::CUmemoryPool,
attribute: sys::CUmemPool_attribute,
mut value: u64,
) -> Result<()> {
Ok(unsafe { result::mem_pool::set_attribute(pool, attribute, (&raw mut value).cast()) }?)
}
fn validate_view<T>(len: usize, bytes: usize) -> Result<()> {
if std::mem::size_of::<T>().checked_mul(len) == Some(bytes) {
Ok(())
} else {
Err(crate::Error::InvalidHostView)
}
}
pub(super) const fn ensure_transfer_size(source: usize, target: usize) -> Result<()> {
if source == target {
Ok(())
} else {
Err(crate::Error::TransferSizeMismatch)
}
}