Skip to main content

cubecl_server/storage/
pinned.rs

1//! Page-locked host memory, and handing a slice of it out as [`Bytes`].
2//!
3//! Pinned pages are what a driver can DMA from without a bounce, so every
4//! backend that stages transfers through the host allocates them. What one
5//! looks like from the outside — a pointer, a length, and a binding that keeps
6//! the allocation alive while a caller holds a slice of it — is the same
7//! whichever driver page-locked it.
8
9use crate::memory_management::ManagedMemoryBinding;
10use cubecl_common::bytes::{AccessError, AccessPolicy, AllocationController, AllocationProperty};
11
12/// The alignment pinned allocations are handed out at.
13///
14/// A `u128`'s worth, which is the widest load a host-side copy will make of
15/// staged bytes.
16pub const PINNED_MEMORY_ALIGNMENT: usize = core::mem::size_of::<u128>();
17
18/// A range of page-locked host memory.
19#[derive(Debug)]
20pub struct PinnedMemoryResource {
21    /// Pointer to the pinned memory buffer.
22    pub ptr: *mut u8,
23    /// Size of the memory resource in bytes.
24    pub size: usize,
25}
26
27// SAFETY: the pointer is to page-locked host memory, which stays valid and
28// pinned whichever thread touches it; access is serialized by the device
29// handle above it.
30unsafe impl Send for PinnedMemoryResource {}
31
32/// Hands out a pinned allocation as [`Bytes`](cubecl_common::bytes::Bytes),
33/// keeping the allocation alive for as long as the bytes are.
34///
35/// The binding is held and never read: dropping it is what returns the pages
36/// to the pool, so a caller that still has the slice still has the memory.
37pub struct PinnedMemoryAllocController {
38    resource: PinnedMemoryResource,
39    /// The memory binding, kept alive until deallocation.
40    _binding: ManagedMemoryBinding,
41}
42
43impl PinnedMemoryAllocController {
44    /// A controller over the pinned allocation `binding` names, resolved to
45    /// `resource`.
46    pub fn init(binding: ManagedMemoryBinding, resource: PinnedMemoryResource) -> Self {
47        Self {
48            _binding: binding,
49            resource,
50        }
51    }
52}
53
54impl AllocationController for PinnedMemoryAllocController {
55    fn alloc_align(&self) -> usize {
56        PINNED_MEMORY_ALIGNMENT
57    }
58
59    fn property(&self) -> AllocationProperty {
60        AllocationProperty::Pinned
61    }
62
63    // Pinned host memory is always host-resident: the policy never forces a
64    // copy here.
65    unsafe fn memory_mut(
66        &mut self,
67        _policy: AccessPolicy,
68    ) -> Result<&mut [core::mem::MaybeUninit<u8>], AccessError> {
69        // A zero-size resource carries a NULL pointer — page-locking nothing
70        // succeeds without allocating — which `from_raw_parts_mut` rejects
71        // even for an empty slice. Hand out an aligned dangling pointer.
72        if self.resource.size == 0 {
73            return Ok(empty_pinned_slice_mut());
74        }
75        // SAFETY:
76        // - the pointer is valid while the binding is alive,
77        // - the resource was allocated with `size` bytes,
78        // - `MaybeUninit<u8>` has the same layout as `u8`,
79        // - the caller promises to write only initialized data into it.
80        Ok(unsafe {
81            core::slice::from_raw_parts_mut(
82                self.resource.ptr as *mut core::mem::MaybeUninit<u8>,
83                self.resource.size,
84            )
85        })
86    }
87
88    fn memory(&self, _policy: AccessPolicy) -> Result<&[core::mem::MaybeUninit<u8>], AccessError> {
89        // See `memory_mut`: a zero-size resource carries a NULL pointer.
90        if self.resource.size == 0 {
91            return Ok(empty_pinned_slice_mut());
92        }
93        // SAFETY: as `memory_mut`, without the write.
94        Ok(unsafe {
95            core::slice::from_raw_parts(
96                self.resource.ptr as *mut core::mem::MaybeUninit<u8>,
97                self.resource.size,
98            )
99        })
100    }
101}
102
103/// An empty slice whose dangling pointer still satisfies
104/// [`PINNED_MEMORY_ALIGNMENT`], matching what `alloc_align` advertises.
105fn empty_pinned_slice_mut<'a>() -> &'a mut [core::mem::MaybeUninit<u8>] {
106    // SAFETY: a dangling, well-aligned, non-null pointer is valid for a
107    // zero-length slice.
108    unsafe {
109        core::slice::from_raw_parts_mut(
110            core::ptr::without_provenance_mut(PINNED_MEMORY_ALIGNMENT),
111            0,
112        )
113    }
114}