Skip to main content

singe_cuda/
memory.rs

1use std::{
2    marker::PhantomData,
3    mem::{self, MaybeUninit},
4    ptr, slice,
5};
6
7use num_enum::{IntoPrimitive, TryFromPrimitive};
8use singe_core::{impl_enum_conversion, impl_enum_display};
9use singe_cuda_sys::{driver, runtime};
10
11use crate::{
12    error::{Error, Result},
13    ipc::IpcMemoryHandle,
14    stream::{GraphRecordable, Stream, StreamCaptureScope, StreamScope},
15    try_ffi,
16    types::DevicePtr,
17    view::{
18        DeviceRepr, DeviceSlice, DeviceSliceMut, DeviceView, DeviceViewMut, ZeroableDeviceRepr,
19    },
20};
21
22#[derive(Debug, Clone, Copy)]
23pub struct MemoryCopyOperation<'a, T> {
24    dst: *mut T,
25    src: *const T,
26    count: usize,
27    kind: MemoryCopyKind,
28    _marker: PhantomData<&'a mut [T]>,
29}
30
31impl<'a, T> MemoryCopyOperation<'a, T> {
32    /// Creates a stream-capture-safe memcpy operation from raw pointers.
33    ///
34    /// # Safety
35    ///
36    /// Capturing this operation stores `dst` and `src` pointer addresses in the
37    /// resulting CUDA graph. The caller must ensure both pointers are valid for
38    /// `count` elements whenever a captured graph using this operation is
39    /// launched. If `dst` is mutable memory, it must also remain exclusive for
40    /// the work ordered by those launches.
41    pub const unsafe fn new(
42        dst: *mut T,
43        src: *const T,
44        count: usize,
45        kind: MemoryCopyKind,
46    ) -> Self {
47        Self {
48            dst,
49            src,
50            count,
51            kind,
52            _marker: PhantomData,
53        }
54    }
55}
56
57unsafe impl<T> GraphRecordable for MemoryCopyOperation<'_, T> {
58    type Output = ();
59
60    fn record(self, scope: &StreamCaptureScope<'_>) -> Result<()> {
61        unsafe {
62            DeviceMemory::<T>::copy_async(self.dst, self.src, self.count, self.kind, scope.stream())
63        }
64    }
65}
66
67#[derive(Debug, Clone, Copy)]
68pub struct MemorySetOperation<'a, T> {
69    dst: *mut T,
70    value: u8,
71    count: usize,
72    _marker: PhantomData<&'a mut [T]>,
73}
74
75impl<'a, T> MemorySetOperation<'a, T> {
76    /// Creates a stream-capture-safe memset operation from a raw pointer.
77    ///
78    /// # Safety
79    ///
80    /// Capturing this operation stores `dst` in the resulting CUDA graph. The
81    /// caller must ensure `dst` is valid for writes of
82    /// `count * size_of::<T>()` bytes whenever a captured graph using this
83    /// operation is launched, and that it remains exclusive for the work ordered
84    /// by those launches.
85    pub const unsafe fn new(dst: *mut T, value: u8, count: usize) -> Self {
86        Self {
87            dst,
88            value,
89            count,
90            _marker: PhantomData,
91        }
92    }
93}
94
95unsafe impl<T> GraphRecordable for MemorySetOperation<'_, T> {
96    type Output = ();
97
98    fn record(self, scope: &StreamCaptureScope<'_>) -> Result<()> {
99        unsafe { DeviceMemory::<T>::set_async(self.dst, self.value, self.count, scope.stream()) }
100    }
101}
102
103/// CUDA memory copy types.
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
105#[repr(u32)]
106#[non_exhaustive]
107pub enum MemoryCopyKind {
108    /// Host -&gt; Host.
109    HostToHost = runtime::cudaMemcpyKind::cudaMemcpyHostToHost as _,
110    /// Host -&gt; Device.
111    HostToDevice = runtime::cudaMemcpyKind::cudaMemcpyHostToDevice as _,
112    /// Device -&gt; Host.
113    DeviceToHost = runtime::cudaMemcpyKind::cudaMemcpyDeviceToHost as _,
114    /// Device -&gt; Device.
115    DeviceToDevice = runtime::cudaMemcpyKind::cudaMemcpyDeviceToDevice as _,
116    /// Direction of the transfer is inferred from the pointer values.
117    /// Requires unified virtual addressing.
118    Default = runtime::cudaMemcpyKind::cudaMemcpyDefault as _,
119}
120
121impl_enum_conversion!(runtime::cudaMemcpyKind, MemoryCopyKind);
122
123impl_enum_display!(MemoryCopyKind, {
124    Self::HostToHost => "cudaMemcpyHostToHost",
125    Self::HostToDevice => "cudaMemcpyHostToDevice",
126    Self::DeviceToHost => "cudaMemcpyDeviceToHost",
127    Self::DeviceToDevice => "cudaMemcpyDeviceToDevice",
128    Self::Default => "cudaMemcpyDefault",
129});
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132#[repr(transparent)]
133pub struct ArrayHandle(runtime::cudaArray_t);
134
135impl ArrayHandle {
136    pub const unsafe fn from_raw(handle: runtime::cudaArray_t) -> Self {
137        Self(handle)
138    }
139
140    pub const fn as_raw(self) -> runtime::cudaArray_t {
141        self.0
142    }
143}
144
145bitflags::bitflags! {
146    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147    pub struct MemoryAttachFlags: u32 {
148        const GLOBAL = driver::CUmemAttach_flags::CU_MEM_ATTACH_GLOBAL as _;
149        const HOST = driver::CUmemAttach_flags::CU_MEM_ATTACH_HOST as _;
150        const SINGLE = driver::CUmemAttach_flags::CU_MEM_ATTACH_SINGLE as _;
151    }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
155#[repr(u32)]
156#[non_exhaustive]
157pub enum MemoryAllocationType {
158    Invalid = driver::CUmemAllocationType::CU_MEM_ALLOCATION_TYPE_INVALID as _,
159    Pinned = driver::CUmemAllocationType::CU_MEM_ALLOCATION_TYPE_PINNED as _,
160    Managed = driver::CUmemAllocationType::CU_MEM_ALLOCATION_TYPE_MANAGED as _,
161    Max = driver::CUmemAllocationType::CU_MEM_ALLOCATION_TYPE_MAX as _,
162}
163
164impl_enum_conversion!(u32, driver::CUmemAllocationType, MemoryAllocationType);
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
167#[repr(u32)]
168#[non_exhaustive]
169pub enum MemoryAllocationHandleType {
170    None = driver::CUmemAllocationHandleType::CU_MEM_HANDLE_TYPE_NONE as _,
171    PosixFileDescriptor =
172        driver::CUmemAllocationHandleType::CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR as _,
173    Win32 = driver::CUmemAllocationHandleType::CU_MEM_HANDLE_TYPE_WIN32 as _,
174    Win32Kmt = driver::CUmemAllocationHandleType::CU_MEM_HANDLE_TYPE_WIN32_KMT as _,
175    Fabric = driver::CUmemAllocationHandleType::CU_MEM_HANDLE_TYPE_FABRIC as _,
176    Max = driver::CUmemAllocationHandleType::CU_MEM_HANDLE_TYPE_MAX as _,
177}
178
179impl_enum_conversion!(
180    u32,
181    driver::CUmemAllocationHandleType,
182    MemoryAllocationHandleType
183);
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
186#[repr(u32)]
187#[non_exhaustive]
188pub enum MemoryAccessFlag {
189    None = driver::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_NONE as _,
190    Read = driver::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READ as _,
191    ReadWrite = driver::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_READWRITE as _,
192    Max = driver::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_MAX as _,
193}
194
195impl_enum_conversion!(u32, driver::CUmemAccess_flags, MemoryAccessFlag);
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
198#[repr(u32)]
199#[non_exhaustive]
200pub enum MemoryPoolAttribute {
201    ReuseFollowEventDependencies =
202        driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_REUSE_FOLLOW_EVENT_DEPENDENCIES as _,
203    ReuseAllowOpportunistic =
204        driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_REUSE_ALLOW_OPPORTUNISTIC as _,
205    ReuseAllowInternalDependencies =
206        driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_REUSE_ALLOW_INTERNAL_DEPENDENCIES as _,
207    ReleaseThreshold = driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_RELEASE_THRESHOLD as _,
208    ReservedMemoryCurrent = driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_RESERVED_MEM_CURRENT as _,
209    ReservedMemoryHigh = driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_RESERVED_MEM_HIGH as _,
210    UsedMemoryCurrent = driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_USED_MEM_CURRENT as _,
211    UsedMemoryHigh = driver::CUmemPool_attribute::CU_MEMPOOL_ATTR_USED_MEM_HIGH as _,
212}
213
214impl_enum_conversion!(u32, driver::CUmemPool_attribute, MemoryPoolAttribute);
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
217#[non_exhaustive]
218pub enum MemoryPoolAttributeValue {
219    Bool(bool),
220    Bytes(u64),
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
224pub struct MemoryAccessDescriptor {
225    pub location: MemoryLocation,
226    pub flags: MemoryAccessFlag,
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
230pub struct MemoryPoolProps {
231    pub alloc_type: MemoryAllocationType,
232    pub handle_type: MemoryAllocationHandleType,
233    pub location: MemoryLocation,
234    pub max_size: usize,
235    pub usage: u16,
236}
237
238#[derive(Debug)]
239pub struct MemoryPool {
240    handle: driver::CUmemoryPool,
241}
242
243impl From<MemoryAccessDescriptor> for driver::CUmemAccessDesc {
244    fn from(value: MemoryAccessDescriptor) -> Self {
245        Self {
246            location: value.location.into(),
247            flags: value.flags.into(),
248        }
249    }
250}
251
252impl From<MemoryPoolProps> for driver::CUmemPoolProps {
253    fn from(value: MemoryPoolProps) -> Self {
254        Self {
255            allocType: value.alloc_type.into(),
256            handleTypes: value.handle_type.into(),
257            location: value.location.into(),
258            win32SecurityAttributes: ptr::null_mut(),
259            maxSize: value.max_size as _,
260            usage: value.usage,
261            reserved: [0; 54],
262        }
263    }
264}
265
266bitflags::bitflags! {
267    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
268    pub struct HostAllocationFlags: u32 {
269        const DEFAULT = runtime::cudaHostAllocDefault;
270        const PORTABLE = runtime::cudaHostAllocPortable;
271        const MAPPED = runtime::cudaHostAllocMapped;
272        const WRITE_COMBINED = runtime::cudaHostAllocWriteCombined;
273    }
274}
275
276bitflags::bitflags! {
277    /// Flags for [`DeviceMemory::register_host`].
278    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
279    pub struct HostRegisterFlags: u32 {
280        const DEFAULT = runtime::cudaHostRegisterDefault;
281        const PORTABLE = runtime::cudaHostRegisterPortable;
282        const MAPPED = runtime::cudaHostRegisterMapped;
283        const IO_MEMORY = runtime::cudaHostRegisterIoMemory;
284        const READ_ONLY = runtime::cudaHostRegisterReadOnly;
285    }
286}
287
288/// CUDA memory types.
289#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TryFromPrimitive, IntoPrimitive)]
290#[repr(u32)]
291#[non_exhaustive]
292pub enum MemoryType {
293    /// Unregistered memory.
294    Unregistered = runtime::cudaMemoryType::cudaMemoryTypeUnregistered as _,
295    /// Host memory.
296    Host = runtime::cudaMemoryType::cudaMemoryTypeHost as _,
297    /// Device memory.
298    Device = runtime::cudaMemoryType::cudaMemoryTypeDevice as _,
299    /// Managed memory.
300    Managed = runtime::cudaMemoryType::cudaMemoryTypeManaged as _,
301}
302
303impl_enum_conversion!(runtime::cudaMemoryType, MemoryType);
304
305impl_enum_display!(MemoryType, {
306    Self::Unregistered => "cudaMemoryTypeUnregistered",
307    Self::Host => "cudaMemoryTypeHost",
308    Self::Device => "cudaMemoryTypeDevice",
309    Self::Managed => "cudaMemoryTypeManaged",
310});
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
313pub struct PointerAttributes {
314    pub memory_type: MemoryType,
315    pub device: i32,
316    pub device_pointer: DevicePtr,
317    pub host_pointer: *mut (),
318}
319
320impl From<runtime::cudaPointerAttributes> for PointerAttributes {
321    fn from(attr: runtime::cudaPointerAttributes) -> Self {
322        Self {
323            memory_type: attr.type_.into(),
324            device: attr.device,
325            device_pointer: unsafe { DevicePtr::from_raw(attr.devicePointer.cast()) },
326            host_pointer: attr.hostPointer.cast(),
327        }
328    }
329}
330
331#[repr(u32)]
332#[derive(
333    Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq, TryFromPrimitive, IntoPrimitive,
334)]
335#[non_exhaustive]
336pub enum MemoryLocationKind {
337    Invalid = driver::CUmemLocationType_enum::CU_MEM_LOCATION_TYPE_INVALID as _,
338    Device = driver::CUmemLocationType_enum::CU_MEM_LOCATION_TYPE_DEVICE as _,
339    Host = driver::CUmemLocationType_enum::CU_MEM_LOCATION_TYPE_HOST as _,
340    Numa = driver::CUmemLocationType_enum::CU_MEM_LOCATION_TYPE_HOST_NUMA as _,
341    NumaCurrent = driver::CUmemLocationType_enum::CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT as _,
342    Max = driver::CUmemLocationType_enum::CU_MEM_LOCATION_TYPE_MAX as _,
343}
344
345impl_enum_conversion!(driver::CUmemLocationType_enum, MemoryLocationKind);
346
347impl_enum_display!(MemoryLocationKind, {
348    Self::Invalid => "CU_MEM_LOCATION_TYPE_INVALID",
349    Self::Device => "CU_MEM_LOCATION_TYPE_DEVICE",
350    Self::Host => "CU_MEM_LOCATION_TYPE_HOST",
351    Self::Numa => "CU_MEM_LOCATION_TYPE_HOST_NUMA",
352    Self::NumaCurrent => "CU_MEM_LOCATION_TYPE_HOST_NUMA_CURRENT",
353    Self::Max => "CU_MEM_LOCATION_TYPE_MAX",
354});
355
356#[derive(Debug, Clone, Copy, Hash, PartialOrd, Ord, PartialEq, Eq)]
357pub struct MemoryLocation {
358    pub kind: MemoryLocationKind,
359    pub id: i32,
360}
361
362impl From<driver::CUmemLocation_st> for MemoryLocation {
363    fn from(s: driver::CUmemLocation_st) -> Self {
364        Self {
365            kind: s.type_.into(),
366            id: unsafe { s.__bindgen_anon_1.id },
367        }
368    }
369}
370
371impl From<MemoryLocation> for driver::CUmemLocation_st {
372    fn from(m: MemoryLocation) -> Self {
373        Self {
374            type_: m.kind.into(),
375            __bindgen_anon_1: driver::CUmemLocation_st__bindgen_ty_1 { id: m.id as _ },
376        }
377    }
378}
379
380impl Default for MemoryLocation {
381    fn default() -> Self {
382        driver::CUmemLocation_st::default().into()
383    }
384}
385
386impl MemoryPool {
387    /// Creates a CUDA memory pool.
388    /// `props` determines the properties of the pool such as the backing device and IPC capabilities.
389    ///
390    /// To create a memory pool for host memory not targeting a specific NUMA node, applications must set [`MemoryPoolProps::location`] to [`MemoryLocationKind::Host`].
391    /// [`MemoryLocation::id`] is ignored for such pools.
392    /// Pools created with [`MemoryLocationKind::Host`] are not IPC-capable and [`MemoryPoolProps::handle_type`] must be [`MemoryAllocationHandleType::None`]; any other value returns [`crate::error::Status::InvalidValue`].
393    /// To create a memory pool targeting a specific host NUMA node, applications must set [`MemoryLocation::kind`] to [`MemoryLocationKind::Numa`] and [`MemoryLocation::id`] must specify the NUMA ID of the host memory node.
394    /// Specifying [`MemoryLocationKind::NumaCurrent`] as [`MemoryLocation::kind`] returns [`crate::error::Status::InvalidValue`].
395    /// By default, the pool's memory is accessible from the device where it is allocated.
396    /// Pools created with [`MemoryLocationKind::Numa`] or [`MemoryLocationKind::Host`] are accessible from the host CPU by default.
397    /// Applications can control the maximum size of the pool by specifying a non-zero value for [`MemoryPoolProps::max_size`].
398    /// A value of 0 uses a system-dependent maximum pool size.
399    ///
400    /// Callers that intend to use [`MemoryAllocationHandleType::Fabric`] based memory sharing must ensure: (1) the `nvidia-caps-imex-channels` character device is created by the driver and is listed under `/proc/devices`; (2) at least one IMEX channel file is accessible to the process.
401    ///
402    /// When exporter and importer CUDA processes have been granted access to the same IMEX channel, they can securely share memory.
403    ///
404    /// The IMEX channel security model works per operating-system account.
405    /// All processes for an account can share memory if that account has access to a valid IMEX channel.
406    /// When isolation between accounts is desired, each account needs a separate IMEX channel.
407    ///
408    /// These channel files exist in `/dev/nvidia-caps-imex-channels/channel*` and can be created using standard OS native calls like `mknod` on Linux.
409    ///
410    /// To create a managed memory pool, applications must set [`MemoryPoolProps::alloc_type`] to [`MemoryAllocationType::Managed`].
411    /// [`MemoryPoolProps::handle_type`] must also be [`MemoryAllocationHandleType::None`] because IPC is not supported.
412    /// For managed memory pools, [`MemoryPoolProps::location`] is treated as the preferred location for all allocations created from the pool.
413    /// An application can also set [`MemoryLocationKind::Invalid`] to indicate no preferred location.
414    /// [`MemoryPoolProps::max_size`] must be set to zero for managed memory pools.
415    /// [`MemoryPoolProps::usage`] must be zero because decompression for managed memory is not supported.
416    /// For managed memory pools, all devices on the system must have non-zero concurrentManagedAccess.
417    /// If not, this call returns [`crate::error::Status::NotSupported`].
418    ///
419    /// Specifying [`MemoryAllocationHandleType::None`] creates a memory pool that does not support IPC.
420    ///
421    /// # Errors
422    ///
423    /// Returns an error if `props` describes an unsupported pool, CUDA cannot
424    /// create the pool, or CUDA returns a null memory-pool handle.
425    pub fn create(props: MemoryPoolProps) -> Result<Self> {
426        let mut handle = ptr::null_mut();
427        let props = driver::CUmemPoolProps::from(props);
428        unsafe {
429            try_ffi!(driver::cuMemPoolCreate(&raw mut handle, &raw const props))?;
430        }
431        if handle.is_null() {
432            return Err(Error::NullHandle);
433        }
434        Ok(Self { handle })
435    }
436
437    /// Supported attributes are:
438    ///
439    /// * [`MemoryPoolAttribute::ReleaseThreshold`]: amount of reserved memory, in bytes, to keep before trying to release memory back to the OS.
440    ///   When more than the release threshold bytes of memory are held by the memory pool, the allocator will try to release memory
441    ///   back to the OS on the next call to stream, event or context synchronize.
442    ///   (default 0)
443    /// * [`MemoryPoolAttribute::ReuseFollowEventDependencies`]: allows [`sys::cuMemAllocAsync`](singe_cuda_sys::driver::cuMemAllocAsync) to use memory asynchronously freed in another stream as long as a stream ordering dependency of the allocating stream on
444    ///   the free action exists.
445    ///   CUDA events and null stream interactions can create the required stream ordered dependencies.
446    ///   (default
447    ///   enabled)
448    /// * [`MemoryPoolAttribute::ReuseAllowOpportunistic`]: allows reuse of already completed frees when there is no dependency between the free and allocation.
449    ///   (default
450    ///   enabled)
451    /// * [`MemoryPoolAttribute::ReuseAllowInternalDependencies`]: allows [`sys::cuMemAllocAsync`](singe_cuda_sys::driver::cuMemAllocAsync) to insert new stream dependencies to establish the stream ordering required to reuse a piece of memory released
452    ///   by [`sys::cuMemFreeAsync`](singe_cuda_sys::driver::cuMemFreeAsync) (default enabled).
453    /// * [`MemoryPoolAttribute::ReservedMemoryHigh`]: resets the high watermark that tracks the amount of backing memory allocated for the memory
454    ///   pool.
455    ///   It is illegal to set this attribute to a non-zero value.
456    /// * [`MemoryPoolAttribute::UsedMemoryHigh`]: resets the high watermark that tracks the amount of used memory allocated for the memory
457    ///   pool.
458    pub fn set_attribute(
459        &mut self,
460        attribute: MemoryPoolAttribute,
461        value: MemoryPoolAttributeValue,
462    ) -> Result<()> {
463        unsafe {
464            match (attribute, value) {
465                (
466                    MemoryPoolAttribute::ReuseFollowEventDependencies
467                    | MemoryPoolAttribute::ReuseAllowOpportunistic
468                    | MemoryPoolAttribute::ReuseAllowInternalDependencies,
469                    MemoryPoolAttributeValue::Bool(value),
470                ) => {
471                    let mut value = u32::from(value);
472                    try_ffi!(driver::cuMemPoolSetAttribute(
473                        self.handle,
474                        attribute.into(),
475                        ptr::from_mut(&mut value).cast(),
476                    ))?;
477                }
478                (
479                    MemoryPoolAttribute::ReleaseThreshold
480                    | MemoryPoolAttribute::ReservedMemoryCurrent
481                    | MemoryPoolAttribute::ReservedMemoryHigh
482                    | MemoryPoolAttribute::UsedMemoryCurrent
483                    | MemoryPoolAttribute::UsedMemoryHigh,
484                    MemoryPoolAttributeValue::Bytes(value),
485                ) => {
486                    let mut value = value;
487                    try_ffi!(driver::cuMemPoolSetAttribute(
488                        self.handle,
489                        attribute.into(),
490                        ptr::from_mut(&mut value).cast(),
491                    ))?;
492                }
493                _ => return Err(Error::InvalidValue),
494            }
495        }
496        Ok(())
497    }
498
499    /// Supported attributes are:
500    ///
501    /// * [`MemoryPoolAttribute::ReleaseThreshold`]: amount of reserved memory, in bytes, to keep before trying to release memory back to the OS.
502    ///   When more than the release threshold bytes of memory are held by the memory pool, the allocator will try to release memory
503    ///   back to the OS on the next call to stream, event or context synchronize.
504    ///   (default 0)
505    /// * [`MemoryPoolAttribute::ReuseFollowEventDependencies`]: allows [`sys::cuMemAllocAsync`](singe_cuda_sys::driver::cuMemAllocAsync) to use memory asynchronously freed in another stream as long as a stream ordering dependency of the allocating stream on
506    ///   the free action exists.
507    ///   CUDA events and null stream interactions can create the required stream ordered dependencies.
508    ///   (default
509    ///   enabled)
510    /// * [`MemoryPoolAttribute::ReuseAllowOpportunistic`]: allows reuse of already completed frees when there is no dependency between the free and allocation.
511    ///   (default
512    ///   enabled)
513    /// * [`MemoryPoolAttribute::ReuseAllowInternalDependencies`]: allows [`sys::cuMemAllocAsync`](singe_cuda_sys::driver::cuMemAllocAsync) to insert new stream dependencies to establish the stream ordering required to reuse a piece of memory released
514    ///   by [`sys::cuMemFreeAsync`](singe_cuda_sys::driver::cuMemFreeAsync) (default enabled).
515    /// * [`MemoryPoolAttribute::ReservedMemoryCurrent`]: backing memory currently allocated for the memory pool.
516    /// * [`MemoryPoolAttribute::ReservedMemoryHigh`]: high watermark of backing memory allocated for the memory pool since the last reset.
517    /// * [`MemoryPoolAttribute::UsedMemoryCurrent`]: memory from the pool that is currently in use by the application.
518    /// * [`MemoryPoolAttribute::UsedMemoryHigh`]: high watermark of memory from the pool that was in use by the application.
519    ///
520    /// # Errors
521    ///
522    /// Returns an error if CUDA Driver cannot report the requested pool attribute.
523    pub fn attribute(&self, attribute: MemoryPoolAttribute) -> Result<MemoryPoolAttributeValue> {
524        unsafe {
525            match attribute {
526                MemoryPoolAttribute::ReuseFollowEventDependencies
527                | MemoryPoolAttribute::ReuseAllowOpportunistic
528                | MemoryPoolAttribute::ReuseAllowInternalDependencies => {
529                    let mut value = 0u32;
530                    try_ffi!(driver::cuMemPoolGetAttribute(
531                        self.handle,
532                        attribute.into(),
533                        ptr::from_mut(&mut value).cast(),
534                    ))?;
535                    Ok(MemoryPoolAttributeValue::Bool(value != 0))
536                }
537                MemoryPoolAttribute::ReleaseThreshold
538                | MemoryPoolAttribute::ReservedMemoryCurrent
539                | MemoryPoolAttribute::ReservedMemoryHigh
540                | MemoryPoolAttribute::UsedMemoryCurrent
541                | MemoryPoolAttribute::UsedMemoryHigh => {
542                    let mut value = 0u64;
543                    try_ffi!(driver::cuMemPoolGetAttribute(
544                        self.handle,
545                        attribute.into(),
546                        ptr::from_mut(&mut value).cast(),
547                    ))?;
548                    Ok(MemoryPoolAttributeValue::Bytes(value))
549                }
550            }
551        }
552    }
553
554    /// Controls visibility of pools between devices.
555    ///
556    /// # Errors
557    ///
558    /// Returns an error if CUDA Driver rejects the access descriptors.
559    pub fn set_access(&mut self, access_descs: &[MemoryAccessDescriptor]) -> Result<()> {
560        let access_descs: Vec<_> = access_descs.iter().copied().map(Into::into).collect();
561        unsafe {
562            try_ffi!(driver::cuMemPoolSetAccess(
563                self.handle,
564                access_descs.as_ptr(),
565                access_descs.len() as _,
566            ))?;
567        }
568        Ok(())
569    }
570
571    /// Returns the accessibility of the pool's memory from the specified location.
572    ///
573    /// # Errors
574    ///
575    /// Returns an error if CUDA Driver cannot report access from `location`.
576    pub fn access(&self, location: MemoryLocation) -> Result<MemoryAccessFlag> {
577        let mut flags = driver::CUmemAccess_flags::CU_MEM_ACCESS_FLAGS_PROT_NONE;
578        let mut location = driver::CUmemLocation_st::from(location);
579        unsafe {
580            try_ffi!(driver::cuMemPoolGetAccess(
581                &raw mut flags,
582                self.handle,
583                &raw mut location,
584            ))?;
585        }
586        Ok(flags.into())
587    }
588
589    /// Releases memory back to the OS until the pool contains fewer than `min_bytes_to_keep` reserved bytes, or there is no more memory that the allocator can safely release.
590    /// The allocator cannot release OS allocations that back outstanding asynchronous allocations.
591    /// The OS allocations may happen at different granularity from the caller's allocations.
592    ///
593    /// * Allocations that have not been freed count as outstanding.
594    /// * Allocations that have been asynchronously freed but whose completion has not been observed on the host, for example by synchronization, can count as outstanding.
595    ///
596    /// # Errors
597    ///
598    /// Returns an error if CUDA cannot trim the pool.
599    pub fn trim_to(&mut self, min_bytes_to_keep: usize) -> Result<()> {
600        unsafe {
601            try_ffi!(driver::cuMemPoolTrimTo(self.handle, min_bytes_to_keep as _))?;
602        }
603        Ok(())
604    }
605
606    pub const fn as_raw(&self) -> driver::CUmemoryPool {
607        self.handle
608    }
609}
610
611impl Drop for MemoryPool {
612    fn drop(&mut self) {
613        unsafe {
614            if let Err(err) = try_ffi!(driver::cuMemPoolDestroy(self.handle)) {
615                #[cfg(debug_assertions)]
616                eprintln!("failed to destroy cuda memory pool: {err}");
617            }
618        }
619    }
620}
621
622/// Represents a region of owned CUDA device memory for elements of type `T`.
623#[derive(Debug)]
624pub struct DeviceMemory<T> {
625    /// Raw pointer to the allocated device memory.
626    ptr: *mut T,
627    /// Number of elements of type `T` allocated.
628    length: usize,
629    /// Marker for the type `T`.
630    _phantom: PhantomData<T>,
631}
632
633impl<T> PartialEq for DeviceMemory<T> {
634    fn eq(&self, other: &Self) -> bool {
635        self.ptr == other.ptr && self.length == other.length
636    }
637}
638
639impl<T> Eq for DeviceMemory<T> {}
640
641#[derive(Debug)]
642pub struct ManagedMemory<T: DeviceRepr> {
643    ptr: *mut T,
644    length: usize,
645    // CUDA tracks the current visibility policy for managed memory separately
646    // from the pointer. Store the last policy requested through this wrapper so
647    // callers can reason about stream attachment without another FFI query.
648    attach_flags: MemoryAttachFlags,
649    _phantom: PhantomData<T>,
650}
651
652/// Associated utility functions.
653impl<T> DeviceMemory<T> {
654    /// Allocates size bytes of linear memory on the device and returns a pointer to the allocated memory.
655    /// The allocated memory is suitably aligned for any kind of variable.
656    /// The memory is not cleared.
657    /// [`DeviceMemory::alloc`] returns [`crate::error::Status::OutOfMemory`] on allocation failure.
658    ///
659    /// The device version of [`DeviceMemory::free`] cannot be used with a pointer allocated using the host API, and vice versa.
660    ///
661    /// # Errors
662    ///
663    /// Returns an error if the requested byte size overflows, CUDA cannot
664    /// allocate device memory, a previous asynchronous launch reports an error,
665    /// or CUDA reports runtime initialization diagnostics such as
666    /// [`crate::error::Status::NotInitialized`], [`crate::error::Status::CallRequiresNewerDriver`],
667    /// or [`crate::error::Status::NoDevice`].
668    ///
669    /// # Safety
670    ///
671    /// The returned pointer is uninitialized device memory. The caller must use
672    /// it only for `count` elements of `T` and eventually free it with a
673    /// compatible CUDA free function.
674    pub unsafe fn alloc(count: usize) -> Result<*mut T> {
675        let Some(bytes) = count.checked_mul(size_of::<T>()) else {
676            return Err(Error::InvalidMemoryAllocationRequest);
677        };
678        let mut p = ptr::null_mut();
679        unsafe {
680            try_ffi!(runtime::cudaMalloc(&raw mut p, bytes as _))?;
681        }
682        Ok(p.cast())
683    }
684
685    pub unsafe fn alloc_managed(count: usize, flags: MemoryAttachFlags) -> Result<*mut T> {
686        let Some(bytes) = count.checked_mul(size_of::<T>()) else {
687            return Err(Error::InvalidMemoryAllocationRequest);
688        };
689        if bytes == 0 {
690            return Ok(ptr::null_mut());
691        }
692        let mut p = ptr::null_mut();
693        unsafe {
694            try_ffi!(runtime::cudaMallocManaged(
695                &raw mut p,
696                bytes as _,
697                flags.bits(),
698            ))?;
699        }
700        Ok(p.cast::<T>())
701    }
702
703    /// Frees the memory space pointed to by `ptr`, which must have been returned by a previous call to one of these allocation functions: [`DeviceMemory::alloc`], [`sys::cudaMallocPitch`](singe_cuda_sys::runtime::cudaMallocPitch), [`DeviceMemory::alloc_managed`], [`DeviceMemory::alloc_async`], or [`sys::cudaMallocFromPoolAsync`](singe_cuda_sys::runtime::cudaMallocFromPoolAsync).
704    ///
705    /// This does not perform implicit synchronization when the pointer was allocated with [`DeviceMemory::alloc_async`] or [`sys::cudaMallocFromPoolAsync`](singe_cuda_sys::runtime::cudaMallocFromPoolAsync).
706    /// Callers must ensure that all accesses to this pointer have completed before invoking [`DeviceMemory::free`].
707    /// For best performance and memory reuse, use [`DeviceMemory::free_async`] to free memory allocated via the stream ordered memory allocator.
708    /// For all other pointers, this call may perform implicit synchronization.
709    ///
710    /// If [`DeviceMemory::free`] has already been called before, an error is returned.
711    /// If `ptr` is null, no operation is performed.
712    /// [`DeviceMemory::free`] returns an error on failure.
713    ///
714    /// The device version of [`DeviceMemory::free`] cannot be used with a pointer allocated using the host API, and vice versa.
715    ///
716    /// # Errors
717    ///
718    /// Returns an error if CUDA cannot free `ptr`, `ptr` has already been
719    /// freed, a previous asynchronous launch reports an error, or CUDA reports
720    /// runtime initialization diagnostics.
721    ///
722    /// # Safety
723    ///
724    /// `ptr` must be null or a live allocation returned by a compatible CUDA
725    /// device allocation function, and no work may access it after it is freed.
726    pub unsafe fn free(ptr: *mut T) -> Result<()> {
727        unsafe {
728            try_ffi!(runtime::cudaFree(ptr.cast()))?;
729        }
730        Ok(())
731    }
732
733    /// Copies `count` elements from `src` to `dst`.
734    /// The transfer direction is specified by [`MemoryCopyKind`].
735    /// [`MemoryCopyKind::Default`] is recommended when unified virtual addressing is available, in which case the transfer direction is inferred from the pointer values.
736    /// Calling [`DeviceMemory::copy`] with `dst` and `src` pointers that do not match the direction of the copy results in undefined behavior.
737    ///
738    /// * Exhibits `synchronous` behavior for most use cases.
739    /// * Memory regions requested must be either entirely registered with CUDA, or in the case of host pageable transfers, not registered
740    ///   at all.
741    ///   Memory regions spanning over allocations that are both registered and not registered with CUDA are not supported and
742    ///   return [`crate::error::Status::InvalidValue`].
743    ///
744    /// # Errors
745    ///
746    /// Returns an error if the requested byte count overflows, CUDA rejects the
747    /// pointer combination or copy kind, a previous asynchronous launch reports
748    /// an error, or CUDA reports runtime initialization diagnostics.
749    ///
750    /// # Safety
751    ///
752    /// `src` and `dst` must be valid for `count` elements of `T` according to
753    /// `kind`, and the source and destination regions must not overlap unless
754    /// CUDA permits that transfer.
755    pub unsafe fn copy(
756        dst: *mut T,
757        src: *const T,
758        count: usize,
759        kind: MemoryCopyKind,
760    ) -> Result<()> {
761        let Some(bytes) = count.checked_mul(size_of::<T>()) else {
762            return Err(Error::InvalidMemoryAllocationRequest);
763        };
764        unsafe {
765            try_ffi!(runtime::cudaMemcpy(
766                dst.cast(),
767                src.cast(),
768                bytes as _,
769                kind.into(),
770            ))?;
771        }
772        Ok(())
773    }
774
775    /// Fills the first `count` bytes of the memory area pointed to by `ptr` with the constant byte `value`.
776    ///
777    /// This call is asynchronous with respect to the host unless `ptr` refers to pinned host memory.
778    ///
779    /// See the CUDA memset synchronization rules for when this operation blocks
780    /// the host.
781    ///
782    /// # Errors
783    ///
784    /// Returns an error if the requested byte count overflows, CUDA rejects the
785    /// pointer or size, a previous asynchronous launch reports an error, or CUDA
786    /// reports runtime initialization diagnostics.
787    ///
788    /// # Safety
789    ///
790    /// `dst` must be valid for writes of `count * size_of::<T>()` bytes and
791    /// must refer to memory that CUDA can memset.
792    pub unsafe fn set(dst: *mut T, value: u8, count: usize) -> Result<()> {
793        let Some(bytes) = count.checked_mul(size_of::<T>()) else {
794            return Err(Error::InvalidMemoryAllocationRequest);
795        };
796        unsafe {
797            try_ffi!(runtime::cudaMemset(dst.cast(), value.into(), bytes as _))?;
798        }
799        Ok(())
800    }
801
802    pub unsafe fn alloc_host(size: usize) -> Result<*mut ()> {
803        let mut ptr = ptr::null_mut();
804        unsafe {
805            try_ffi!(runtime::cudaMallocHost(
806                &raw mut ptr,
807                size as runtime::size_t
808            ))?;
809        }
810        Ok(ptr.cast())
811    }
812
813    /// Frees host memory returned by [`DeviceMemory::alloc_host`] or [`DeviceMemory::alloc_pinned`].
814    ///
815    /// # Errors
816    ///
817    /// Returns an error if CUDA cannot free the host allocation, a previous
818    /// asynchronous launch reports an error, or CUDA reports runtime
819    /// initialization diagnostics.
820    ///
821    /// # Safety
822    ///
823    /// `ptr` must be null or a live host allocation returned by a compatible
824    /// CUDA host allocation function.
825    pub unsafe fn free_host(ptr: *mut ()) -> Result<()> {
826        unsafe { try_ffi!(runtime::cudaFreeHost(ptr.cast())) }
827    }
828
829    /// Allocates size bytes of host memory that is page-locked and accessible to the device.
830    /// The driver tracks the allocated virtual memory ranges and automatically accelerates calls such as [`DeviceMemory::copy`].
831    /// Since the memory can be accessed directly by the device, it can be read or written with much higher bandwidth than pageable memory obtained with functions such as `malloc()`.
832    /// Allocating excessive amounts of pinned memory may degrade system performance, since it reduces the amount of memory available to the system for paging.
833    /// As a result, use this sparingly to allocate staging areas for data exchange between host and device.
834    ///
835    /// `flags` selects allocation options:
836    ///
837    /// * [`HostAllocationFlags::DEFAULT`]: equivalent to [`DeviceMemory::alloc_host`].
838    /// * [`HostAllocationFlags::PORTABLE`]: the memory returned by this call is considered pinned memory by all CUDA contexts, not just the one that performed
839    ///   the allocation.
840    /// * [`HostAllocationFlags::MAPPED`]: maps the allocation into the CUDA address space.
841    ///   The device pointer to the memory may be obtained by calling [`sys::cudaHostGetDevicePointer`](singe_cuda_sys::runtime::cudaHostGetDevicePointer).
842    /// * [`HostAllocationFlags::WRITE_COMBINED`]: allocates the memory as write-combined (WC).
843    ///   WC memory can be transferred across the PCI Express bus more quickly on some
844    ///   system configurations, but cannot be read efficiently by most CPUs.
845    ///   WC memory is a good option for buffers written
846    ///   by the CPU and read by the device via mapped pinned memory or host-&gt;device transfers.
847    ///
848    /// All of these flags are orthogonal to one another: a developer may allocate memory that is portable, mapped and/or write-combined with no restrictions.
849    ///
850    /// For [`HostAllocationFlags::MAPPED`] to have any effect, the CUDA context must support [`ContextFlags::MAP_HOST`](crate::context::ContextFlags::MAP_HOST), which can be checked via [`Device::flags`](crate::device::Device::flags).
851    /// [`ContextFlags::MAP_HOST`](crate::context::ContextFlags::MAP_HOST) is implicitly set for contexts created via the runtime API.
852    ///
853    /// [`HostAllocationFlags::MAPPED`] may be specified on CUDA contexts for devices that do not support mapped pinned memory.
854    /// The failure is deferred to [`sys::cudaHostGetDevicePointer`](singe_cuda_sys::runtime::cudaHostGetDevicePointer) because the memory may be mapped into other CUDA contexts via [`HostAllocationFlags::PORTABLE`].
855    ///
856    /// Memory allocated by this method must be freed with [`DeviceMemory::free_host`].
857    ///
858    /// # Errors
859    ///
860    /// Returns an error if CUDA cannot allocate pinned host memory, a previous
861    /// asynchronous launch reports an error, or CUDA reports runtime
862    /// initialization diagnostics.
863    ///
864    /// # Safety
865    ///
866    /// The returned pointer is uninitialized host memory. The caller must ensure
867    /// it is accessed within `size` bytes and freed with [`DeviceMemory::free_host`].
868    pub unsafe fn alloc_pinned(size: usize, flags: HostAllocationFlags) -> Result<*mut ()> {
869        let mut ptr = ptr::null_mut();
870        unsafe {
871            try_ffi!(runtime::cudaHostAlloc(
872                &raw mut ptr,
873                size as _,
874                flags.bits()
875            ))?;
876        }
877        Ok(ptr.cast())
878    }
879
880    /// Page-locks the memory range specified by `ptr` and `size`, and maps it for the devices selected by `flags`.
881    /// This memory range also is added to the same tracking mechanism as [`DeviceMemory::alloc_pinned`] to automatically accelerate calls to functions such as [`DeviceMemory::copy`].
882    /// Since the memory can be accessed directly by the device, it can be read or written with much higher bandwidth than pageable memory that has not been registered.
883    /// Page-locking excessive amounts of memory may degrade system performance, since it reduces the amount of memory available to the system for paging.
884    /// As a result, use this sparingly to register staging areas for data exchange between host and device.
885    ///
886    /// On systems where [`DeviceProperties::pageable_memory_access_uses_host_page_tables`](crate::device::DeviceProperties::pageable_memory_access_uses_host_page_tables) is enabled, [`DeviceMemory::register_host`] does not page-lock the memory range specified by `ptr` and instead only populates unpopulated pages.
887    ///
888    /// [`DeviceMemory::register_host`] is supported only on I/O coherent devices where [`DeviceProperties::host_register_supported`](crate::device::DeviceProperties::host_register_supported) is enabled.
889    ///
890    /// `flags` selects registration options:
891    ///
892    /// * [`HostRegisterFlags::DEFAULT`]: on a system with unified virtual addressing, the memory is both mapped and portable.
893    ///   On a system with no unified virtual addressing, the memory is neither mapped nor portable.
894    ///
895    /// * [`HostRegisterFlags::PORTABLE`]: the memory returned by this call is considered pinned memory by all CUDA contexts, not just the one that performed
896    ///   the allocation.
897    ///
898    /// * [`HostRegisterFlags::MAPPED`]: maps the allocation into the CUDA address space.
899    ///   The device pointer to the memory may be obtained by calling [`sys::cudaHostGetDevicePointer`](singe_cuda_sys::runtime::cudaHostGetDevicePointer).
900    ///
901    /// * [`HostRegisterFlags::IO_MEMORY`]: the passed memory pointer is treated as pointing to some memory-mapped I/O space, for example belonging to a third-party PCIe device,
902    ///   and it is marked as non-cache-coherent and contiguous.
903    ///
904    /// * [`HostRegisterFlags::READ_ONLY`]: the passed memory pointer is treated as pointing to memory that is considered read-only by the device.
905    ///   On platforms without
906    ///   [`DeviceProperties::pageable_memory_access_uses_host_page_tables`](crate::device::DeviceProperties::pageable_memory_access_uses_host_page_tables), this flag is required to register memory mapped to the CPU as read-only.
907    ///   Query support with [`DeviceProperties::host_register_read_only_supported`](crate::device::DeviceProperties::host_register_read_only_supported).
908    ///   Using this flag with a current context associated with a device that does not have this attribute set makes [`DeviceMemory::register_host`] return [`crate::error::Status::NotSupported`].
909    ///
910    /// All of these flags are orthogonal to one another: a developer may page-lock memory that is portable or mapped with no restrictions.
911    ///
912    /// The CUDA context must have been created with [`ContextFlags::MAP_HOST`](crate::context::ContextFlags::MAP_HOST) for [`HostRegisterFlags::MAPPED`] to have any effect.
913    ///
914    /// [`HostRegisterFlags::MAPPED`] may be specified on CUDA contexts for devices that do not support mapped pinned memory.
915    /// The failure is deferred to [`sys::cudaHostGetDevicePointer`](singe_cuda_sys::runtime::cudaHostGetDevicePointer) because the memory may be mapped into other CUDA contexts via [`HostRegisterFlags::PORTABLE`].
916    ///
917    /// On devices where [`DeviceProperties::can_use_host_pointer_for_registered_mem`](crate::device::DeviceProperties::can_use_host_pointer_for_registered_mem) is enabled, the memory can also be accessed from the device using the original host pointer.
918    /// The device pointer returned by [`sys::cudaHostGetDevicePointer`](singe_cuda_sys::runtime::cudaHostGetDevicePointer) may or may not match the original host pointer and depends on the devices visible to the application.
919    /// If all devices visible to the application have a non-zero value for the device attribute, the device pointer returned by [`sys::cudaHostGetDevicePointer`](singe_cuda_sys::runtime::cudaHostGetDevicePointer) matches the original pointer.
920    /// If any device visible to the application has a zero value for the device attribute, the device pointer returned by [`sys::cudaHostGetDevicePointer`](singe_cuda_sys::runtime::cudaHostGetDevicePointer) does not match the original host pointer, but is suitable for use on all devices provided Unified Virtual Addressing is enabled.
921    /// In such systems, it is valid to access the memory using either pointer on devices that have a non-zero value for the device attribute.
922    /// Such devices must access the memory through only one of the two pointers, not both.
923    ///
924    /// The memory page-locked by this method must be unregistered with [`DeviceMemory::unregister_host`].
925    ///
926    /// # Errors
927    ///
928    /// Returns an error if CUDA cannot register the host range, the pointer,
929    /// size, or flags are invalid, a previous asynchronous launch reports an
930    /// error, or CUDA reports runtime initialization diagnostics.
931    ///
932    /// # Safety
933    ///
934    /// `ptr..ptr + size` must be a valid host memory range and must remain valid
935    /// until it is unregistered.
936    pub unsafe fn register_host(ptr: *mut (), size: usize, flags: HostRegisterFlags) -> Result<()> {
937        unsafe {
938            try_ffi!(runtime::cudaHostRegister(
939                ptr.cast(),
940                size as _,
941                flags.bits()
942            ))?;
943        }
944        Ok(())
945    }
946
947    /// Unmaps the memory range whose base address is specified by `ptr`, and makes it pageable again.
948    ///
949    /// The base address must be the same one specified to [`DeviceMemory::register_host`].
950    ///
951    /// # Errors
952    ///
953    /// Returns an error if CUDA cannot unregister the host range, `ptr` is not
954    /// the base address of a registered range, a previous asynchronous launch
955    /// reports an error, or CUDA reports runtime initialization diagnostics.
956    ///
957    /// # Safety
958    ///
959    /// `ptr` must be the base address of a host range registered with
960    /// [`DeviceMemory::register_host`] and must not be unregistered twice.
961    pub unsafe fn unregister_host(ptr: *mut ()) -> Result<()> {
962        unsafe { try_ffi!(runtime::cudaHostUnregister(ptr.cast())) }
963    }
964
965    /// Returns the total amount of memory available to the current context and the amount of memory free on the device.
966    /// CUDA is not guaranteed to be able to allocate all of the memory that the OS reports as free.
967    /// In a multi-tenant situation, the free-memory estimate is prone to a race condition: an allocation or free by another process or thread between estimation and reporting can make the reported free value differ from actual free memory.
968    ///
969    /// The integrated GPU on Tegra shares memory with CPU and other component of the SoC.
970    /// The free and total values returned by this call exclude the SWAP memory space maintained by the OS on some platforms.
971    /// The OS may move some of the memory pages into swap area as the GPU or CPU allocate or access memory.
972    /// See Tegra app note on how to calculate total and free memory on Tegra.
973    ///
974    /// # Errors
975    ///
976    /// Returns an error if CUDA cannot query memory information, a previous
977    /// asynchronous launch reports an error, or CUDA reports runtime
978    /// initialization diagnostics.
979    pub fn memory_info() -> Result<(usize, usize)> {
980        let mut free: runtime::size_t = 0;
981        let mut total: runtime::size_t = 0;
982        unsafe {
983            try_ffi!(runtime::cudaMemGetInfo(&raw mut free, &raw mut total))?;
984        }
985        Ok((free as usize, total as usize))
986    }
987
988    /// Returns the attributes of `ptr`.
989    /// If `ptr` was not allocated in, mapped by, or registered with a context that supports unified addressing, [`crate::error::Status::InvalidValue`] is returned.
990    ///
991    /// In CUDA 11.0 and later, passing a host pointer reports [`MemoryType::Unregistered`] in [`PointerAttributes::memory_type`].
992    ///
993    /// * [`PointerAttributes::memory_type`] identifies the type of memory.
994    ///   It can be [`MemoryType::Unregistered`] for unregistered host memory, [`MemoryType::Host`] for registered host memory, [`MemoryType::Device`] for device memory, or [`MemoryType::Managed`] for managed memory.
995    ///
996    /// * [`PointerAttributes::device`] is the device against which `ptr` was allocated.
997    ///   If `ptr` has memory type [`MemoryType::Device`], this identifies the device on which the memory physically resides.
998    ///   If `ptr` has memory type [`MemoryType::Host`], this identifies the device that was current when the allocation was made, and if that device is deinitialized then
999    ///   this allocation will vanish with that device's state.
1000    ///
1001    /// * [`PointerAttributes::device_pointer`] is the device pointer alias through which the memory referred to by `ptr` may be accessed on the current device.
1002    ///   If the memory referred to by `ptr` cannot be accessed directly by the current device then this is null.
1003    ///
1004    /// * [`PointerAttributes::host_pointer`] is the host pointer alias through which the memory referred to by `ptr` may be accessed on the host.
1005    ///   If the memory referred to by `ptr` cannot be accessed directly by the host then this is null.
1006    ///
1007    /// # Errors
1008    ///
1009    /// Returns an error if CUDA cannot query attributes for `ptr`, `ptr` is not
1010    /// known to a unified-addressing context, or CUDA reports runtime
1011    /// initialization diagnostics.
1012    pub fn pointer_attributes(ptr: *const T) -> Result<PointerAttributes> {
1013        let mut attr_ffi = MaybeUninit::<runtime::cudaPointerAttributes>::uninit();
1014        unsafe {
1015            try_ffi!(runtime::cudaPointerGetAttributes(
1016                attr_ffi.as_mut_ptr(),
1017                ptr.cast(),
1018            ))?;
1019            // Safety: FFI call successful, attr_ffi is initialized.
1020            Ok(attr_ffi.assume_init().into())
1021        }
1022    }
1023
1024    pub unsafe fn alloc_async(count: usize, stream: &Stream) -> Result<*mut T> {
1025        let Some(bytes) = count.checked_mul(size_of::<T>()) else {
1026            return Err(Error::InvalidMemoryAllocationRequest);
1027        };
1028        if bytes == 0 {
1029            return Ok(ptr::null_mut());
1030        }
1031        let mut p = ptr::null_mut();
1032        unsafe {
1033            try_ffi!(runtime::cudaMallocAsync(
1034                &raw mut p,
1035                bytes as _,
1036                stream.as_raw()
1037            ))?;
1038        }
1039        Ok(p.cast::<T>())
1040    }
1041
1042    /// Inserts a free operation into `stream`.
1043    /// The allocation must not be accessed after stream execution reaches the free.
1044    /// After this call returns, accessing the memory from any subsequent work launched on the GPU or querying its pointer attributes results in undefined behavior.
1045    ///
1046    /// During stream capture, this creates a free node and must therefore be passed the address of a graph allocation.
1047    ///
1048    /// # Errors
1049    ///
1050    /// Returns an error if CUDA cannot enqueue the free on `stream`, `ptr` is
1051    /// invalid for asynchronous freeing, a previous asynchronous launch reports
1052    /// an error, or CUDA reports runtime initialization diagnostics.
1053    ///
1054    /// # Safety
1055    ///
1056    /// `ptr` must be null or a live stream-ordered CUDA allocation. No work may
1057    /// access it after `stream` reaches the enqueued free.
1058    pub unsafe fn free_async(ptr: *mut T, stream: &Stream) -> Result<()> {
1059        if ptr.is_null() {
1060            return Ok(());
1061        }
1062        unsafe { try_ffi!(runtime::cudaFreeAsync(ptr.cast(), stream.as_raw())) }
1063    }
1064
1065    pub unsafe fn copy_async(
1066        dst: *mut T,
1067        src: *const T,
1068        count: usize,
1069        kind: MemoryCopyKind,
1070        stream: &Stream,
1071    ) -> Result<()> {
1072        if count == 0 {
1073            return Ok(());
1074        }
1075        let Some(bytes) = count.checked_mul(size_of::<T>()) else {
1076            return Err(Error::InvalidMemoryAllocationRequest);
1077        };
1078        unsafe {
1079            try_ffi!(runtime::cudaMemcpyAsync(
1080                dst.cast(),
1081                src.cast(),
1082                bytes as _,
1083                kind.into(),
1084                stream.as_raw(),
1085            ))?;
1086        }
1087        Ok(())
1088    }
1089
1090    /// Fills the first `count` bytes of the memory area pointed to by `ptr` with the constant byte `value`.
1091    ///
1092    /// [`DeviceMemory::set_async`] is asynchronous with respect to the host, so the call may return before the memset is complete.
1093    /// The operation can optionally be associated with a stream by passing a non-zero stream argument.
1094    /// If `stream` is non-zero, the operation may overlap with operations in other streams.
1095    ///
1096    /// The device version only handles device-to-device copies and cannot be given local or shared pointers.
1097    ///
1098    /// See the CUDA memset synchronization rules for when this operation blocks
1099    /// the host.
1100    ///
1101    /// # Errors
1102    ///
1103    /// Returns an error if the requested byte count overflows, CUDA cannot
1104    /// enqueue the memset on `stream`, a previous asynchronous launch reports an
1105    /// error, or CUDA reports runtime initialization diagnostics.
1106    ///
1107    /// # Safety
1108    ///
1109    /// `dst` must be valid for writes of `count * size_of::<T>()` bytes until
1110    /// `stream` reaches the enqueued memset.
1111    pub unsafe fn set_async(dst: *mut T, value: u8, count: usize, stream: &Stream) -> Result<()> {
1112        if count == 0 {
1113            return Ok(());
1114        }
1115        let Some(bytes) = count.checked_mul(size_of::<T>()) else {
1116            return Err(Error::InvalidMemoryAllocationRequest);
1117        };
1118        unsafe {
1119            try_ffi!(runtime::cudaMemsetAsync(
1120                dst.cast(),
1121                value.into(),
1122                bytes as _,
1123                stream.as_raw(),
1124            ))?;
1125        }
1126        Ok(())
1127    }
1128
1129    /// Prefetches memory to the specified destination location.
1130    /// `ptr` is the base device pointer of the memory to be prefetched, `location` specifies the destination location, `count` specifies the number of bytes to copy, and `stream` is the stream in which the operation is enqueued.
1131    /// The memory range must refer to managed memory allocated via [`DeviceMemory::alloc_managed`] or declared via `__managed__` variables. It may also refer to memory allocated from a managed memory pool, or to system-allocated memory on systems where [`DeviceProperties::pageable_memory_access`](crate::device::DeviceProperties::pageable_memory_access) is enabled.
1132    ///
1133    /// Setting [`MemoryLocation::kind`](crate::memory::MemoryLocation::kind) to [`MemoryLocationKind::Device`] prefetches memory to the GPU identified by [`MemoryLocation::id`](crate::memory::MemoryLocation::id). That device, and the device associated with `stream`, must support concurrent managed access.
1134    /// Setting [`MemoryLocation::kind`](crate::memory::MemoryLocation::kind) to [`MemoryLocationKind::Host`] prefetches data to host memory.
1135    /// Applications can request prefetching memory to a specific host NUMA node by using [`MemoryLocationKind::Numa`] with a valid NUMA node identifier, or to the NUMA node closest to the current thread's CPU by using [`MemoryLocationKind::NumaCurrent`].
1136    /// When [`MemoryLocation::kind`](crate::memory::MemoryLocation::kind) is [`MemoryLocationKind::Host`] or [`MemoryLocationKind::NumaCurrent`], [`MemoryLocation::id`](crate::memory::MemoryLocation::id) is ignored.
1137    ///
1138    /// The start and end addresses of the memory range are rounded down and up, respectively, to CPU page-size alignment before the prefetch operation is enqueued in the stream.
1139    ///
1140    /// If no physical memory has been allocated for this region, CUDA populates and maps it on the destination device.
1141    /// If there is insufficient memory to prefetch the desired region, the Unified Memory driver may evict pages from other [`DeviceMemory::alloc_managed`] allocations to host memory to make room.
1142    /// Device memory allocated using [`DeviceMemory::alloc`] or [`sys::cudaMallocArray`](singe_cuda_sys::runtime::cudaMallocArray) is not evicted.
1143    ///
1144    /// By default, mappings to the previous location of the migrated pages are removed and mappings for the new location are only set up at the destination.
1145    /// The exact behavior also depends on the settings applied to this memory range via `cuMemAdvise` as described below:
1146    ///
1147    /// If read-mostly advice was set on any subset of this memory range, then that subset will create a read-only copy of the pages at the destination location.
1148    /// If the destination location is a host NUMA node, any pages of that subset that are already in another host NUMA node are transferred to the destination.
1149    ///
1150    /// If preferred-location advice was set on any subset of this memory range, then the pages will migrate to `location` even if it is not the preferred location of every page in the range.
1151    ///
1152    /// If accessed-by advice was set on any subset of this memory range, then mappings to those pages from all appropriate processors are updated to refer to the new location if establishing such a mapping is possible.
1153    /// Otherwise, those mappings are cleared.
1154    ///
1155    /// This is not required for correctness; it improves performance by allowing the application to migrate data to a suitable location before access.
1156    /// Memory accesses to this range are always coherent and are allowed even when the data is actively being migrated.
1157    ///
1158    /// This call is asynchronous with respect to the host and all work on other devices.
1159    ///
1160    /// # Errors
1161    ///
1162    /// Returns an error if CUDA cannot enqueue the prefetch on `stream`, the
1163    /// memory range or destination location is invalid, a previous asynchronous
1164    /// launch reports an error, or CUDA reports runtime initialization
1165    /// diagnostics.
1166    pub fn prefetch_async(
1167        ptr: DevicePtr,
1168        count: usize,
1169        location: MemoryLocation,
1170        stream: &Stream,
1171    ) -> Result<()> {
1172        if count == 0 {
1173            return Ok(());
1174        }
1175        unsafe {
1176            try_ffi!(runtime::cudaMemPrefetchAsync(
1177                ptr.as_ptr() as _,
1178                count as _,
1179                location.into(),
1180                0, // flags
1181                stream.as_raw()
1182            ))?;
1183        }
1184        Ok(())
1185    }
1186}
1187
1188// Safety: DeviceMemory owns a CUDA allocation and does not provide CPU
1189// references into the allocation. Moving the owner to another thread is safe
1190// when `T: Send` because CUDA allocation/free operations are synchronized by
1191// the CUDA context APIs used by this wrapper. Shared references are safe when
1192// `T: Sync` because `&DeviceMemory<T>` only exposes raw device pointers and
1193// read-only metadata; mutation of device memory still requires CUDA operations
1194// whose safety contracts are enforced by the calling APIs.
1195unsafe impl<T: Send> Send for DeviceMemory<T> {}
1196unsafe impl<T: Sync> Sync for DeviceMemory<T> {}
1197
1198// Safety: managed memory has the same Rust-side ownership properties as
1199// DeviceMemory. CUDA maintains coherence for managed allocations across host
1200// threads and devices; Rust references into the allocation are not exposed by
1201// this wrapper.
1202unsafe impl<T: DeviceRepr + Send> Send for ManagedMemory<T> {}
1203unsafe impl<T: DeviceRepr + Sync> Sync for ManagedMemory<T> {}
1204
1205impl<T> DeviceMemory<T> {
1206    /// Takes ownership of an existing device allocation.
1207    ///
1208    /// # Safety
1209    ///
1210    /// `ptr` must be null for an empty allocation or point to `length` live
1211    /// elements allocated by `cudaMallocManaged` or another CUDA allocation
1212    /// function compatible with `cudaFree`. `length * size_of::<T>()` must fit
1213    /// in `usize`.
1214    /// No other owner may free the pointer while the returned value is alive.
1215    pub unsafe fn from_raw_parts(ptr: *mut T, length: usize) -> Self {
1216        Self {
1217            ptr,
1218            length,
1219            _phantom: PhantomData,
1220        }
1221    }
1222
1223    pub fn into_raw_parts(self) -> (*mut T, usize) {
1224        let ptr = self.ptr;
1225        let length = self.length;
1226        mem::forget(self);
1227        (ptr, length)
1228    }
1229
1230    pub fn create(length: usize) -> Result<Self> {
1231        let size_t = size_of::<T>();
1232
1233        if size_t == 0 {
1234            if length == 0 {
1235                return Ok(Self {
1236                    ptr: ptr::null_mut(), // No allocation needed for ZSTs with count 0
1237                    length: 0,
1238                    _phantom: PhantomData,
1239                });
1240            }
1241            return Err(Error::InvalidMemoryAllocationRequest);
1242        }
1243
1244        // Ensure allocation size doesn't overflow usize when calculating bytes internally in `alloc`.
1245        if length > (usize::MAX / size_t) {
1246            return Err(Error::InvalidMemoryAllocationRequest);
1247        }
1248
1249        if length == 0 {
1250            Ok(Self {
1251                ptr: ptr::null_mut(),
1252                length: 0,
1253                _phantom: PhantomData,
1254            })
1255        } else {
1256            let device_ptr = unsafe { Self::alloc(length)? };
1257
1258            Ok(Self {
1259                ptr: device_ptr,
1260                length,
1261                _phantom: PhantomData,
1262            })
1263        }
1264    }
1265
1266    pub fn zeroes(length: usize) -> Result<Self> {
1267        let mut mem = Self::create(length)?;
1268        mem.set_zeroes()?;
1269        Ok(mem)
1270    }
1271
1272    pub fn from_slice(v: &[T]) -> Result<Self> {
1273        let mut mem = Self::create(v.len())?;
1274        mem.copy_from_host(v)?;
1275        Ok(mem)
1276    }
1277
1278    /// # Safety
1279    ///
1280    /// The caller must ensure `v` remains valid and unmodified until `stream`
1281    /// has completed the transfer.
1282    ///
1283    /// # Errors
1284    ///
1285    /// Returns an error if CUDA cannot allocate device memory or enqueue the
1286    /// host-to-device copy.
1287    pub unsafe fn from_slice_async(v: &[T], stream: &Stream) -> Result<Self> {
1288        let mut mem = Self::create(v.len())?;
1289        unsafe {
1290            mem.copy_from_host_async_unchecked(v, stream)?;
1291        }
1292        Ok(mem)
1293    }
1294
1295    pub const fn len(&self) -> usize {
1296        self.length
1297    }
1298
1299    pub const fn is_empty(&self) -> bool {
1300        self.length == 0
1301    }
1302
1303    pub fn byte_len(&self) -> usize {
1304        self.length
1305            .checked_mul(size_of::<T>())
1306            .expect("device memory byte length overflow")
1307    }
1308
1309    pub const fn as_ptr(&self) -> *const T {
1310        self.ptr
1311    }
1312
1313    pub const fn as_mut_ptr(&self) -> *mut T {
1314        self.ptr
1315    }
1316
1317    pub const fn as_device_ptr(&self) -> DevicePtr {
1318        unsafe { DevicePtr::from_raw(self.ptr.cast()) }
1319    }
1320
1321    pub fn copy_from_host(&mut self, host_slice: &[T]) -> Result<()> {
1322        if host_slice.len() != self.length {
1323            return Err(Error::InvalidMemoryAccess);
1324        }
1325        if self.length == 0 {
1326            return Ok(());
1327        }
1328        unsafe {
1329            Self::copy(
1330                self.ptr,
1331                host_slice.as_ptr(),
1332                self.length,
1333                MemoryCopyKind::HostToDevice,
1334            )
1335        }
1336    }
1337
1338    pub fn copy_from_host_async<'scope, 'env>(
1339        &mut self,
1340        host_slice: &'env [T],
1341        stream: &StreamScope<'scope, 'env>,
1342    ) -> Result<()> {
1343        unsafe { self.copy_from_host_async_unchecked(host_slice, stream.stream()) }
1344    }
1345
1346    /// # Safety
1347    ///
1348    /// The caller must ensure `self` and `host_slice` both remain valid until
1349    /// `stream` has completed the transfer.
1350    pub unsafe fn copy_from_host_async_unchecked(
1351        &mut self,
1352        host_slice: &[T],
1353        stream: &Stream,
1354    ) -> Result<()> {
1355        if host_slice.len() != self.len() {
1356            return Err(Error::InvalidMemoryAccess);
1357        }
1358        if self.is_empty() {
1359            return Ok(());
1360        }
1361        unsafe {
1362            Self::copy_async(
1363                self.as_mut_ptr(),
1364                host_slice.as_ptr(),
1365                self.len(),
1366                MemoryCopyKind::HostToDevice,
1367                stream,
1368            )
1369        }
1370    }
1371
1372    /// Returns a capture operation that copies from host memory into this device allocation.
1373    ///
1374    /// # Safety
1375    ///
1376    /// Capturing this operation stores the host and device pointer addresses in
1377    /// the resulting CUDA graph. The caller must ensure `self` and `host_slice`
1378    /// remain valid whenever a captured graph using this operation is launched.
1379    /// The destination allocation must remain exclusive for the work ordered by
1380    /// those launches.
1381    pub unsafe fn copy_from_host_operation<'a>(
1382        &'a mut self,
1383        host_slice: &'a [T],
1384    ) -> Result<MemoryCopyOperation<'a, T>> {
1385        if host_slice.len() != self.len() {
1386            return Err(Error::InvalidMemoryAccess);
1387        }
1388        Ok(unsafe {
1389            MemoryCopyOperation::new(
1390                self.as_mut_ptr(),
1391                host_slice.as_ptr(),
1392                self.len(),
1393                MemoryCopyKind::HostToDevice,
1394            )
1395        })
1396    }
1397
1398    pub fn copy_to_host(&self, host_slice: &mut [T]) -> Result<()> {
1399        if host_slice.len() != self.length {
1400            return Err(Error::InvalidMemoryAccess);
1401        }
1402        if self.length == 0 {
1403            return Ok(());
1404        }
1405        unsafe {
1406            Self::copy(
1407                host_slice.as_mut_ptr(),
1408                self.ptr,
1409                self.length,
1410                MemoryCopyKind::DeviceToHost,
1411            )
1412        }
1413    }
1414
1415    pub fn copy_to_host_async<'scope, 'env>(
1416        &self,
1417        host_slice: &'env mut [T],
1418        stream: &StreamScope<'scope, 'env>,
1419    ) -> Result<()> {
1420        unsafe { self.copy_to_host_async_unchecked(host_slice, stream.stream()) }
1421    }
1422
1423    /// # Safety
1424    ///
1425    /// The caller must ensure `self` and `host_slice` both remain valid until
1426    /// `stream` has completed the transfer.
1427    pub unsafe fn copy_to_host_async_unchecked(
1428        &self,
1429        host_slice: &mut [T],
1430        stream: &Stream,
1431    ) -> Result<()> {
1432        if host_slice.len() != self.len() {
1433            return Err(Error::InvalidMemoryAccess);
1434        }
1435        if self.is_empty() {
1436            return Ok(());
1437        }
1438        unsafe {
1439            Self::copy_async(
1440                host_slice.as_mut_ptr(),
1441                self.as_ptr(),
1442                self.len(),
1443                MemoryCopyKind::DeviceToHost,
1444                stream,
1445            )
1446        }
1447    }
1448
1449    /// Returns a capture operation that copies this allocation into host memory.
1450    ///
1451    /// # Safety
1452    ///
1453    /// Capturing this operation stores the device and host pointer addresses in
1454    /// the resulting CUDA graph. The caller must ensure `self` and `host_slice`
1455    /// remain valid whenever a captured graph using this operation is launched.
1456    /// The host destination must remain exclusive for the work ordered by those
1457    /// launches.
1458    pub unsafe fn copy_to_host_operation<'a>(
1459        &'a self,
1460        host_slice: &'a mut [T],
1461    ) -> Result<MemoryCopyOperation<'a, T>> {
1462        if host_slice.len() != self.len() {
1463            return Err(Error::InvalidMemoryAccess);
1464        }
1465        Ok(unsafe {
1466            MemoryCopyOperation::new(
1467                host_slice.as_mut_ptr(),
1468                self.as_ptr(),
1469                self.len(),
1470                MemoryCopyKind::DeviceToHost,
1471            )
1472        })
1473    }
1474
1475    pub fn copy_to_host_vec(&self) -> Result<Vec<T>> {
1476        if size_of::<T>() == 0 {
1477            return Err(Error::InvalidMemoryAllocationRequest);
1478        }
1479
1480        if self.length == 0 {
1481            return Ok(Vec::new());
1482        }
1483
1484        let mut host_vec = Vec::<T>::with_capacity(self.length);
1485
1486        unsafe {
1487            Self::copy(
1488                host_vec.as_mut_ptr(),
1489                self.ptr,
1490                self.length,
1491                MemoryCopyKind::DeviceToHost,
1492            )?;
1493
1494            host_vec.set_len(self.length);
1495        }
1496
1497        Ok(host_vec)
1498    }
1499
1500    pub fn copy_from_device(&mut self, src: &Self) -> Result<()> {
1501        if src.len() != self.length {
1502            return Err(Error::InvalidMemoryAccess);
1503        }
1504        if self.length == 0 {
1505            return Ok(());
1506        }
1507        unsafe {
1508            Self::copy(
1509                self.ptr,
1510                src.as_ptr(),
1511                self.length,
1512                MemoryCopyKind::DeviceToDevice,
1513            )
1514        }
1515    }
1516
1517    pub fn copy_from_device_async<'scope, 'env>(
1518        &mut self,
1519        src: &Self,
1520        stream: &StreamScope<'scope, 'env>,
1521    ) -> Result<()> {
1522        unsafe { self.copy_from_device_async_unchecked(src, stream.stream()) }
1523    }
1524
1525    /// # Safety
1526    ///
1527    /// The caller must ensure `self` and `src` both remain valid until
1528    /// `stream` has completed the transfer.
1529    pub unsafe fn copy_from_device_async_unchecked(
1530        &mut self,
1531        src: &Self,
1532        stream: &Stream,
1533    ) -> Result<()> {
1534        if src.len() != self.len() {
1535            return Err(Error::InvalidMemoryAccess);
1536        }
1537        if self.is_empty() {
1538            return Ok(());
1539        }
1540        unsafe {
1541            Self::copy_async(
1542                self.as_mut_ptr(),
1543                src.as_ptr(),
1544                self.len(),
1545                MemoryCopyKind::DeviceToDevice,
1546                stream,
1547            )
1548        }
1549    }
1550
1551    /// Returns a capture operation that copies from another device allocation into this allocation.
1552    ///
1553    /// # Safety
1554    ///
1555    /// Capturing this operation stores both device pointer addresses in the
1556    /// resulting CUDA graph. The caller must ensure `self` and `src` remain
1557    /// valid whenever a captured graph using this operation is launched. The
1558    /// destination allocation must remain exclusive for the work ordered by
1559    /// those launches.
1560    pub unsafe fn copy_from_device_operation<'a>(
1561        &'a mut self,
1562        src: &'a Self,
1563    ) -> Result<MemoryCopyOperation<'a, T>> {
1564        if src.len() != self.len() {
1565            return Err(Error::InvalidMemoryAccess);
1566        }
1567        Ok(unsafe {
1568            MemoryCopyOperation::new(
1569                self.as_mut_ptr(),
1570                src.as_ptr(),
1571                self.len(),
1572                MemoryCopyKind::DeviceToDevice,
1573            )
1574        })
1575    }
1576
1577    pub fn set_zeroes(&mut self) -> Result<()> {
1578        if self.length == 0 {
1579            return Ok(());
1580        }
1581        unsafe { Self::set(self.ptr, 0, self.length) }
1582    }
1583
1584    pub fn set_value(&mut self, value: u8) -> Result<()> {
1585        if self.length == 0 {
1586            return Ok(());
1587        }
1588        unsafe { Self::set(self.ptr, value, self.length) }
1589    }
1590
1591    pub fn set_value_async<'scope, 'env>(
1592        &mut self,
1593        value: u8,
1594        stream: &StreamScope<'scope, 'env>,
1595    ) -> Result<()> {
1596        unsafe { self.set_value_async_unchecked(value, stream.stream()) }
1597    }
1598
1599    /// # Safety
1600    ///
1601    /// The caller must ensure `self` remains valid until `stream` has
1602    /// completed the memset.
1603    ///
1604    /// # Errors
1605    ///
1606    /// Returns an error if CUDA cannot enqueue the memset on `stream`.
1607    pub unsafe fn set_value_async_unchecked(&mut self, value: u8, stream: &Stream) -> Result<()> {
1608        if self.is_empty() {
1609            return Ok(());
1610        }
1611        unsafe { Self::set_async(self.as_mut_ptr(), value, self.len(), stream) }
1612    }
1613
1614    /// Returns a capture operation that fills this device allocation with `value`.
1615    ///
1616    /// # Safety
1617    ///
1618    /// Capturing this operation stores this allocation's pointer address in the
1619    /// resulting CUDA graph. The caller must ensure `self` remains valid and
1620    /// exclusive whenever a captured graph using this operation is launched.
1621    pub unsafe fn set_value_operation<'a>(&'a mut self, value: u8) -> MemorySetOperation<'a, T> {
1622        unsafe { MemorySetOperation::new(self.as_mut_ptr(), value, self.len()) }
1623    }
1624
1625    /// Takes a pointer to the base of an existing device memory allocation created with [`DeviceMemory::alloc`] and exports it for use in another process.
1626    /// This is a lightweight operation and may be called multiple times on an allocation without adverse effects.
1627    ///
1628    /// If a region of memory is freed with [`DeviceMemory::free`] and a subsequent call to [`DeviceMemory::alloc`] returns memory with the same device address, [`DeviceMemory::ipc_handle`] returns a unique handle for the new memory.
1629    ///
1630    /// IPC is restricted to devices with unified-addressing support on Linux and Windows.
1631    /// IPC on Windows is supported for compatibility but is not recommended because of its performance cost.
1632    /// Check device IPC support through the device properties exposed by this crate, for example [`DeviceProperties::ipc_event_supported`](crate::device::DeviceProperties::ipc_event_supported).
1633    ///
1634    /// # Errors
1635    ///
1636    /// Returns an error if the allocation is empty, CUDA cannot export an IPC
1637    /// handle for the allocation, or CUDA reports runtime initialization
1638    /// diagnostics.
1639    pub fn ipc_handle(&self) -> Result<IpcMemoryHandle> {
1640        if self.is_empty() {
1641            // Cannot get handle for null pointer / zero size? Check docs.
1642            return Err(Error::InvalidMemoryAccess);
1643        }
1644        let mut handle = MaybeUninit::uninit();
1645        unsafe {
1646            try_ffi!(runtime::cudaIpcGetMemHandle(
1647                handle.as_mut_ptr(),
1648                self.as_ptr().cast_mut().cast(),
1649            ))?;
1650            Ok(IpcMemoryHandle::from_raw(handle.assume_init()))
1651        }
1652    }
1653
1654    pub fn try_clone(&self) -> Result<Self> {
1655        if self.length == 0 || size_of::<T>() == 0 {
1656            return Ok(Self {
1657                ptr: ptr::null_mut(),
1658                length: self.length,
1659                _phantom: PhantomData,
1660            });
1661        }
1662
1663        let new_mem = Self::create(self.length)?;
1664
1665        unsafe {
1666            Self::copy(
1667                new_mem.as_mut_ptr(),
1668                self.as_ptr(),
1669                self.length,
1670                MemoryCopyKind::DeviceToDevice,
1671            )?;
1672        }
1673
1674        Ok(new_mem)
1675    }
1676}
1677
1678impl<T> Clone for DeviceMemory<T> {
1679    fn clone(&self) -> Self {
1680        match self.try_clone() {
1681            Ok(new_mem) => new_mem,
1682            Err(err) => {
1683                #[cfg(debug_assertions)]
1684                eprintln!("device memory clone failed: {err}");
1685                Self {
1686                    ptr: ptr::null_mut(),
1687                    length: 0,
1688                    _phantom: PhantomData,
1689                }
1690            }
1691        }
1692    }
1693}
1694
1695impl<T> Drop for DeviceMemory<T> {
1696    fn drop(&mut self) {
1697        if self.ptr.is_null() {
1698            return;
1699        }
1700
1701        // debug_assert!(
1702        //     unsafe { free(self.ptr) }.is_ok(),
1703        //     "failed to free device memory at {:#x}",
1704        //     self.ptr as usize
1705        // );
1706        if let Err(err) = unsafe { Self::free(self.ptr) } {
1707            #[cfg(debug_assertions)]
1708            eprintln!("failed to free device memory: {err}");
1709            return;
1710        }
1711
1712        self.ptr = ptr::null_mut();
1713        self.length = 0;
1714    }
1715}
1716
1717impl<T: DeviceRepr> ManagedMemory<T> {
1718    /// Allocates typed CUDA managed memory and records its initial attach mode.
1719    ///
1720    /// # Errors
1721    ///
1722    /// Returns an error if the requested byte size overflows, CUDA cannot
1723    /// allocate managed memory, or a non-empty zero-sized allocation is
1724    /// requested.
1725    pub fn create(length: usize, attach_flags: MemoryAttachFlags) -> Result<Self> {
1726        if size_of::<T>() == 0 {
1727            return if length == 0 {
1728                Ok(Self {
1729                    ptr: ptr::null_mut(),
1730                    length,
1731                    attach_flags,
1732                    _phantom: PhantomData,
1733                })
1734            } else {
1735                Err(Error::InvalidMemoryAllocationRequest)
1736            };
1737        }
1738
1739        let ptr = unsafe { DeviceMemory::<T>::alloc_managed(length, attach_flags)? };
1740        Ok(Self {
1741            ptr,
1742            length,
1743            attach_flags,
1744            _phantom: PhantomData,
1745        })
1746    }
1747
1748    pub fn zeroes(length: usize, attach_flags: MemoryAttachFlags) -> Result<Self>
1749    where
1750        T: ZeroableDeviceRepr,
1751    {
1752        let mut memory = Self::create(length, attach_flags)?;
1753        memory.set_zeroes()?;
1754        Ok(memory)
1755    }
1756
1757    /// Takes ownership of an existing managed allocation.
1758    ///
1759    /// # Safety
1760    ///
1761    /// `ptr` must be null for an empty allocation or point to `length` live
1762    /// elements allocated by a CUDA allocation function compatible with
1763    /// [`DeviceMemory::free`]. `length * size_of::<T>()` must fit in `usize`.
1764    /// No other owner may free the pointer while the returned value is alive.
1765    pub unsafe fn from_raw_parts(
1766        ptr: *mut T,
1767        length: usize,
1768        attach_flags: MemoryAttachFlags,
1769    ) -> Self {
1770        Self {
1771            ptr,
1772            length,
1773            attach_flags,
1774            _phantom: PhantomData,
1775        }
1776    }
1777
1778    pub fn into_raw_parts(self) -> (*mut T, usize, MemoryAttachFlags) {
1779        let ptr = self.ptr;
1780        let length = self.length;
1781        let attach_flags = self.attach_flags;
1782        mem::forget(self);
1783        (ptr, length, attach_flags)
1784    }
1785
1786    pub const fn len(&self) -> usize {
1787        self.length
1788    }
1789
1790    pub const fn is_empty(&self) -> bool {
1791        self.length == 0
1792    }
1793
1794    pub fn byte_len(&self) -> usize {
1795        self.length
1796            .checked_mul(size_of::<T>())
1797            .expect("managed memory byte length overflow")
1798    }
1799
1800    pub const fn attach_flags(&self) -> MemoryAttachFlags {
1801        self.attach_flags
1802    }
1803
1804    pub const fn as_ptr(&self) -> *const T {
1805        self.ptr
1806    }
1807
1808    pub const fn as_mut_ptr(&mut self) -> *mut T {
1809        self.ptr
1810    }
1811
1812    pub fn view(&self) -> DeviceView<'_, T> {
1813        // The ManagedMemory owner guarantees the pointer remains live for the
1814        // borrowed view lifetime.
1815        unsafe { DeviceView::from_raw_parts(self.ptr, self.length) }
1816    }
1817
1818    pub fn view_mut(&mut self) -> DeviceViewMut<'_, T> {
1819        // &mut self guarantees unique access to the represented range.
1820        unsafe { DeviceViewMut::from_raw_parts(self.ptr, self.length) }
1821    }
1822
1823    /// Returns a host slice over this managed allocation.
1824    ///
1825    /// # Safety
1826    ///
1827    /// The caller must ensure no GPU work or other CPU reference can
1828    /// concurrently mutate the same memory for the returned lifetime, and that
1829    /// the allocation is accessible from the host at the point of access.
1830    pub unsafe fn as_host_slice(&self) -> &[T] {
1831        if self.is_empty() {
1832            return &[];
1833        }
1834        unsafe { slice::from_raw_parts(self.ptr, self.length) }
1835    }
1836
1837    /// Returns a mutable host slice over this managed allocation.
1838    ///
1839    /// # Safety
1840    ///
1841    /// The caller must ensure no GPU work or other CPU reference can
1842    /// concurrently access the same memory for the returned lifetime, and that
1843    /// the allocation is accessible from the host at the point of access.
1844    pub unsafe fn as_host_slice_mut(&mut self) -> &mut [T] {
1845        if self.is_empty() {
1846            return &mut [];
1847        }
1848        unsafe { slice::from_raw_parts_mut(self.ptr, self.length) }
1849    }
1850
1851    pub fn set_zeroes(&mut self) -> Result<()>
1852    where
1853        T: ZeroableDeviceRepr,
1854    {
1855        if self.is_empty() {
1856            return Ok(());
1857        }
1858        unsafe { DeviceMemory::<T>::set(self.ptr, 0, self.length) }
1859    }
1860
1861    pub fn prefetch_to(&self, location: MemoryLocation, stream: &Stream) -> Result<()> {
1862        DeviceMemory::<T>::prefetch_async(
1863            unsafe { DevicePtr::from_raw(self.ptr.cast::<()>()) },
1864            self.byte_len(),
1865            location,
1866            stream,
1867        )
1868    }
1869
1870    pub fn attach_to_stream(&mut self, stream: &Stream, flags: MemoryAttachFlags) -> Result<()> {
1871        stream.context().bind()?;
1872        unsafe {
1873            try_ffi!(runtime::cudaStreamAttachMemAsync(
1874                stream.as_raw(),
1875                self.ptr.cast(),
1876                self.byte_len() as _,
1877                flags.bits(),
1878            ))?;
1879        }
1880        self.attach_flags = flags;
1881        Ok(())
1882    }
1883}
1884
1885impl<T: DeviceRepr> DeviceSlice<T> for ManagedMemory<T> {
1886    fn as_device_ptr(&self) -> *const T {
1887        self.ptr
1888    }
1889
1890    fn len(&self) -> usize {
1891        self.length
1892    }
1893}
1894
1895impl<T: DeviceRepr> DeviceSliceMut<T> for ManagedMemory<T> {
1896    fn as_device_mut_ptr(&mut self) -> *mut T {
1897        self.ptr
1898    }
1899}
1900
1901impl<T: DeviceRepr> Drop for ManagedMemory<T> {
1902    fn drop(&mut self) {
1903        if self.ptr.is_null() {
1904            return;
1905        }
1906
1907        if let Err(err) = unsafe { DeviceMemory::<T>::free(self.ptr) } {
1908            #[cfg(debug_assertions)]
1909            eprintln!("failed to free managed memory: {err}");
1910            return;
1911        }
1912
1913        self.ptr = ptr::null_mut();
1914        self.length = 0;
1915    }
1916}
1917
1918#[cfg(all(test, feature = "testing"))]
1919mod tests {
1920    use super::*;
1921    use crate::testing;
1922
1923    #[test]
1924    fn it_works() -> Result<()> {
1925        unsafe {
1926            let host_in = [1, 2, 3];
1927
1928            let device_ptr = DeviceMemory::alloc(3)?;
1929
1930            DeviceMemory::copy(
1931                device_ptr,
1932                host_in.as_ptr(),
1933                3,
1934                MemoryCopyKind::HostToDevice,
1935            )?;
1936            let mut host_out = [0, 0, 0];
1937            DeviceMemory::copy(
1938                host_out.as_mut_ptr(),
1939                device_ptr,
1940                3,
1941                MemoryCopyKind::DeviceToHost,
1942            )?;
1943            assert_eq!(host_out, host_in);
1944
1945            DeviceMemory::free(device_ptr)?;
1946        }
1947        Ok(())
1948    }
1949
1950    #[test]
1951    fn test_scoped_async_copy_round_trip() -> Result<()> {
1952        let (_lock, ctx) = testing::bootstrap()?;
1953        let stream = ctx.create_stream()?;
1954
1955        let host_in = [4_i32, 5, 6];
1956        let mut device = DeviceMemory::create(host_in.len())?;
1957        let mut host_out = [0_i32; 3];
1958
1959        stream.sync_scope(|scope| {
1960            device.copy_from_host_async(&host_in, scope)?;
1961            device.copy_to_host_async(&mut host_out, scope)
1962        })?;
1963
1964        assert_eq!(host_out, host_in);
1965
1966        Ok(())
1967    }
1968
1969    #[test]
1970    fn managed_memory_tracks_metadata_and_views() -> Result<()> {
1971        let mut backing = [1_u32, 2, 3, 4];
1972        let mut managed = unsafe {
1973            ManagedMemory::from_raw_parts(
1974                backing.as_mut_ptr(),
1975                backing.len(),
1976                MemoryAttachFlags::HOST,
1977            )
1978        };
1979
1980        assert_eq!(managed.len(), backing.len());
1981        assert_eq!(managed.byte_len(), backing.len() * size_of::<u32>());
1982        assert_eq!(managed.attach_flags(), MemoryAttachFlags::HOST);
1983        assert_eq!(managed.view().len(), backing.len());
1984        assert_eq!(managed.view_mut().len(), backing.len());
1985
1986        unsafe {
1987            assert_eq!(managed.as_host_slice(), &[1, 2, 3, 4]);
1988            managed.as_host_slice_mut()[2] = 9;
1989        }
1990        assert_eq!(backing[2], 9);
1991
1992        let (ptr, length, flags) = managed.into_raw_parts();
1993        assert_eq!(ptr, backing.as_mut_ptr());
1994        assert_eq!(length, backing.len());
1995        assert_eq!(flags, MemoryAttachFlags::HOST);
1996
1997        Ok(())
1998    }
1999}