1mod pinned_staging;
4mod pool;
5mod ranges;
6
7pub(crate) use self::pinned_staging::PinnedUploadStagingPool;
8pub use self::pinned_staging::{
9 CudaPinnedUploadOperationGuard, CudaPinnedUploadStagingCheckout,
10 CudaPinnedUploadStagingPoolDiagnostics, CudaPinnedUploadStagingPoolLimits,
11};
12#[cfg(test)]
13pub(crate) use self::pool::copy_pooled_bytes_to_vec_uninit;
14#[cfg(test)]
15pub(crate) use self::pool::pool_fit_buffer_index_by_len;
16pub(crate) use self::pool::{
17 copy_pooled_bytes_to_vec_uninit_with_budget, pooled_device_buffer, CudaBufferPoolReuseGuard,
18};
19pub use self::pool::{
20 CudaBufferPool, CudaBufferPoolDiagnostics, CudaBufferPoolLimits, CudaBufferPoolTakeTrace,
21 CudaPooledDeviceBuffer,
22};
23pub(crate) use self::ranges::CheckedDeviceBufferRanges;
24
25#[cfg(test)]
26use crate::context::validate_non_null_pinned_host_allocation;
27use crate::{
28 bytes::f32_slice_as_bytes, context::CudaContext, driver::CuDevicePtr, error::CudaError,
29};
30use std::ffi::c_void;
31
32impl CudaContext {
33 pub fn upload(&self, bytes: &[u8]) -> Result<CudaDeviceBuffer, CudaError> {
35 let mut ptr = 0;
36 let buffer = if bytes.is_empty() {
37 self.inner.set_current()?;
38 CudaDeviceBuffer {
39 context: self.clone(),
40 ptr,
41 len: bytes.len(),
42 }
43 } else {
44 self.inner.with_current_stateful_operation(|| {
45 self.inner.driver.check("cuMemAlloc_v2", unsafe {
48 (self.inner.driver.cu_mem_alloc)(&raw mut ptr, bytes.len())
49 })?;
50 crate::context::validate_device_allocation(ptr, bytes.len())
51 })?;
52
53 CudaDeviceBuffer {
54 context: self.clone(),
55 ptr,
56 len: bytes.len(),
57 }
58 };
59
60 if !bytes.is_empty() {
61 self.inner.with_current_resource_operation(|| {
62 self.inner.driver.check("cuMemcpyHtoD_v2", unsafe {
65 (self.inner.driver.cu_memcpy_htod)(
66 ptr,
67 bytes.as_ptr().cast::<c_void>(),
68 bytes.len(),
69 )
70 })
71 })?;
72 }
73
74 Ok(buffer)
75 }
76
77 pub fn upload_f32(&self, samples: &[f32]) -> Result<CudaDeviceBuffer, CudaError> {
79 self.upload(f32_slice_as_bytes(samples))
80 }
81
82 pub fn allocate(&self, len: usize) -> Result<CudaDeviceBuffer, CudaError> {
84 let mut ptr = 0;
85 if len != 0 {
86 self.inner.with_current_stateful_operation(|| {
87 self.inner.driver.check("cuMemAlloc_v2", unsafe {
90 (self.inner.driver.cu_mem_alloc)(&raw mut ptr, len)
91 })?;
92 crate::context::validate_device_allocation(ptr, len)
93 })?;
94 } else {
95 self.inner.set_current()?;
96 }
97 Ok(CudaDeviceBuffer {
98 context: self.clone(),
99 ptr,
100 len,
101 })
102 }
103
104 #[cfg(test)]
106 pub(crate) fn pinned_host_buffer(&self, len: usize) -> Result<CudaPinnedHostBuffer, CudaError> {
107 let mut ptr = std::ptr::null_mut();
108 if len != 0 {
109 self.inner.with_current_stateful_operation(|| {
110 self.inner.driver.check("cuMemHostAlloc", unsafe {
113 (self.inner.driver.cu_mem_host_alloc)(&raw mut ptr, len, 0)
114 })?;
115 validate_non_null_pinned_host_allocation(ptr.cast::<u8>(), len).map(|_| ())
116 })?;
117 } else {
118 self.inner.set_current()?;
119 }
120 Ok(CudaPinnedHostBuffer {
121 context: self.clone(),
122 ptr: ptr.cast::<u8>(),
123 len,
124 })
125 }
126
127 pub fn buffer_pool(&self) -> CudaBufferPool {
129 CudaBufferPool::new(self.clone())
130 }
131
132 pub fn best_fit_buffer_pool(&self) -> CudaBufferPool {
135 CudaBufferPool::new_size_buckets(self.clone())
136 }
137}
138
139#[cfg(test)]
141#[derive(Debug)]
142pub(crate) struct CudaPinnedHostBuffer {
143 pub(crate) context: CudaContext,
144 pub(crate) ptr: *mut u8,
145 pub(crate) len: usize,
146}
147
148#[cfg(test)]
149impl CudaPinnedHostBuffer {
150 pub(crate) fn as_slice(&self) -> &[u8] {
152 if self.len == 0 {
153 &[]
154 } else {
155 unsafe { std::slice::from_raw_parts(self.ptr.cast_const(), self.len) }
157 }
158 }
159
160 pub(crate) fn as_mut_slice(&mut self) -> &mut [u8] {
162 if self.len == 0 {
163 &mut []
164 } else {
165 unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
168 }
169 }
170}
171
172#[cfg(test)]
173impl Drop for CudaPinnedHostBuffer {
174 fn drop(&mut self) {
175 if !self.ptr.is_null() {
176 let free_result = self.context.inner.with_current_stateful_operation(|| {
177 self.context.inner.driver.check("cuMemFreeHost", unsafe {
180 (self.context.inner.driver.cu_mem_free_host)(self.ptr.cast())
181 })
182 });
183 if free_result.is_err() {
184 std::mem::forget(self.context.clone());
185 }
186 }
187 }
188}
189
190#[cfg(test)]
193unsafe impl Send for CudaPinnedHostBuffer {}
194
195#[derive(Debug)]
197pub struct CudaDeviceBuffer {
198 pub(crate) context: CudaContext,
199 pub(crate) ptr: CuDevicePtr,
200 pub(crate) len: usize,
201}
202
203#[doc(hidden)]
204#[derive(Clone, Copy, Debug)]
206pub struct CudaDeviceBufferView<'a, T> {
207 pub(crate) ptr: CuDevicePtr,
208 pub(crate) len: usize,
209 pub(crate) _marker: std::marker::PhantomData<&'a T>,
210}
211
212impl<T> CudaDeviceBufferView<'_, T> {
213 pub fn device_ptr(&self) -> u64 {
215 self.ptr
216 }
217
218 pub fn len(&self) -> usize {
220 self.len
221 }
222
223 pub fn is_empty(&self) -> bool {
225 self.len == 0
226 }
227}
228
229#[doc(hidden)]
230#[derive(Debug)]
232pub struct CudaDeviceBufferViewMut<'a, T> {
233 pub(crate) ptr: CuDevicePtr,
234 pub(crate) len: usize,
235 pub(crate) _marker: std::marker::PhantomData<&'a mut T>,
236}
237
238#[doc(hidden)]
243#[derive(Debug)]
244pub struct CudaExternalDeviceBufferViewMut<'a> {
245 context: CudaContext,
246 ptr: CuDevicePtr,
247 len: usize,
248 _exclusive: std::marker::PhantomData<&'a mut ()>,
249}
250
251impl<'a> CudaExternalDeviceBufferViewMut<'a> {
252 pub unsafe fn from_raw_parts<Owner>(
264 context: &CudaContext,
265 ptr: u64,
266 len: usize,
267 required_alignment: usize,
268 _managed_owner: &'a mut Owner,
269 ) -> Result<Self, CudaError> {
270 if len == 0 {
271 return Err(CudaError::InvalidArgument {
272 message: "external CUDA buffer must not be empty".to_string(),
273 });
274 }
275 if ptr == 0 {
276 return Err(CudaError::InvalidArgument {
277 message: "external CUDA buffer pointer must not be null".to_string(),
278 });
279 }
280 if required_alignment == 0 || !required_alignment.is_power_of_two() {
281 return Err(CudaError::InvalidArgument {
282 message: "external CUDA buffer alignment must be a nonzero power of two"
283 .to_string(),
284 });
285 }
286 let len_u64 = u64::try_from(len).map_err(|_| CudaError::LengthTooLarge { len })?;
287 ptr.checked_add(len_u64)
288 .ok_or(CudaError::LengthTooLarge { len })?;
289 let ptr = context.inner.resolve_pointer_for_context(ptr)?;
290 if !ptr.is_multiple_of(required_alignment as u64) {
291 return Err(CudaError::InvalidArgument {
292 message: format!(
293 "external CUDA buffer pointer {ptr:#x} is not aligned to {required_alignment} bytes"
294 ),
295 });
296 }
297 ptr.checked_add(len_u64)
298 .ok_or(CudaError::LengthTooLarge { len })?;
299 Ok(Self {
300 context: context.clone(),
301 ptr,
302 len,
303 _exclusive: std::marker::PhantomData,
304 })
305 }
306
307 pub fn context(&self) -> &CudaContext {
309 &self.context
310 }
311
312 pub fn device_ptr(&self) -> u64 {
314 self.ptr
315 }
316
317 pub fn byte_len(&self) -> usize {
319 self.len
320 }
321}
322
323impl<T> CudaDeviceBufferViewMut<'_, T> {
324 pub fn device_ptr(&self) -> u64 {
326 self.ptr
327 }
328
329 pub fn len(&self) -> usize {
331 self.len
332 }
333
334 pub fn is_empty(&self) -> bool {
336 self.len == 0
337 }
338}
339
340#[doc(hidden)]
341#[derive(Clone, Copy, Debug, Eq, PartialEq)]
343pub struct CudaDeviceBufferRange {
344 pub offset: usize,
346 pub len: usize,
348}
349
350impl CudaDeviceBuffer {
351 pub(crate) fn is_owned_by(&self, context: &CudaContext) -> bool {
352 self.context.is_same_context(context)
353 }
354
355 pub fn context(&self) -> CudaContext {
357 self.context.clone()
358 }
359
360 pub fn device_ptr(&self) -> u64 {
362 self.ptr
363 }
364
365 pub fn byte_len(&self) -> usize {
367 self.len
368 }
369
370 #[doc(hidden)]
371 pub fn typed_view<T>(&self) -> Result<CudaDeviceBufferView<'_, T>, CudaError> {
373 let element_size = std::mem::size_of::<T>();
374 if element_size == 0 || !self.len.is_multiple_of(element_size) {
375 return Err(CudaError::LengthNotElementAligned {
376 bytes: self.len,
377 element_size,
378 });
379 }
380 Ok(CudaDeviceBufferView {
381 ptr: self.ptr,
382 len: self.len / element_size,
383 _marker: std::marker::PhantomData,
384 })
385 }
386
387 #[doc(hidden)]
388 pub fn typed_view_mut<T>(&mut self) -> Result<CudaDeviceBufferViewMut<'_, T>, CudaError> {
390 let element_size = std::mem::size_of::<T>();
391 if element_size == 0 || !self.len.is_multiple_of(element_size) {
392 return Err(CudaError::LengthNotElementAligned {
393 bytes: self.len,
394 element_size,
395 });
396 }
397 Ok(CudaDeviceBufferViewMut {
398 ptr: self.ptr,
399 len: self.len / element_size,
400 _marker: std::marker::PhantomData,
401 })
402 }
403
404 pub fn copy_to_host(&self, out: &mut [u8]) -> Result<(), CudaError> {
406 if out.len() < self.len {
407 return Err(CudaError::OutputTooSmall {
408 required: self.len,
409 have: out.len(),
410 });
411 }
412 if self.len == 0 {
413 return Ok(());
414 }
415
416 self.context.inner.with_current_resource_operation(|| {
417 self.context.inner.driver.check("cuMemcpyDtoH_v2", unsafe {
420 (self.context.inner.driver.cu_memcpy_dtoh)(
421 out.as_mut_ptr().cast::<c_void>(),
422 self.ptr,
423 self.len,
424 )
425 })
426 })
427 }
428
429 pub fn copy_range_to_host(&self, offset: usize, out: &mut [u8]) -> Result<(), CudaError> {
431 self.copy_byte_range_to_host_elements(offset, out)
432 }
433
434 pub fn copy_range_to_host_uninit(
436 &self,
437 offset: usize,
438 out: &mut [std::mem::MaybeUninit<u8>],
439 ) -> Result<(), CudaError> {
440 self.copy_byte_range_to_host_elements(offset, out)
441 }
442
443 fn copy_byte_range_to_host_elements<T>(
444 &self,
445 offset: usize,
446 out: &mut [T],
447 ) -> Result<(), CudaError> {
448 let byte_len = out
449 .len()
450 .checked_mul(std::mem::size_of::<T>())
451 .ok_or(CudaError::LengthTooLarge { len: usize::MAX })?;
452 let end = offset
453 .checked_add(byte_len)
454 .ok_or(CudaError::LengthTooLarge { len: usize::MAX })?;
455 if end > self.len {
456 return Err(CudaError::OutputTooSmall {
457 required: end,
458 have: self.len,
459 });
460 }
461 if byte_len == 0 {
462 return Ok(());
463 }
464
465 let source = self
466 .ptr
467 .checked_add(
468 u64::try_from(offset).map_err(|_| CudaError::LengthTooLarge { len: offset })?,
469 )
470 .ok_or(CudaError::LengthTooLarge { len: usize::MAX })?;
471 self.context.inner.with_current_resource_operation(|| {
472 self.context.inner.driver.check("cuMemcpyDtoH_v2", unsafe {
475 (self.context.inner.driver.cu_memcpy_dtoh)(
476 out.as_mut_ptr().cast::<c_void>(),
477 source,
478 byte_len,
479 )
480 })
481 })
482 }
483}
484
485impl Drop for CudaDeviceBuffer {
486 fn drop(&mut self) {
487 if self.ptr != 0 {
488 let free_result = self.context.inner.with_current_stateful_operation(|| {
489 let status = unsafe { (self.context.inner.driver.cu_mem_free)(self.ptr) };
492 self.context.inner.driver.check("cuMemFree_v2", status)
493 });
494 if free_result.is_err() {
495 std::mem::forget(self.context.clone());
499 }
500 }
501 }
502}
503
504pub(crate) fn checked_image_words(
505 width: u32,
506 height: u32,
507 channels: usize,
508) -> Result<usize, CudaError> {
509 width
510 .try_into()
511 .ok()
512 .and_then(|width: usize| width.checked_mul(height as usize))
513 .and_then(|pixels| pixels.checked_mul(channels))
514 .ok_or(CudaError::ImageTooLarge {
515 width,
516 height,
517 channels,
518 })
519}