Skip to main content

oxicuda_memory/
buffer_view.rs

1//! Type-safe buffer reinterpretation for device memory.
2//!
3//! This module provides [`BufferView`] and [`BufferViewMut`], which allow
4//! reinterpreting a [`DeviceBuffer<T>`] as a different element type `U`
5//! without copying data. This is useful for viewing a buffer of `f32`
6//! values as `u32` (e.g., for bitwise operations in a kernel), or for
7//! interpreting raw byte buffers as structured types.
8//!
9//! # Size and alignment constraints
10//!
11//! The total byte size of the original buffer must be evenly divisible
12//! by `std::mem::size_of::<U>()`, and the buffer's device pointer must be
13//! aligned to `std::mem::align_of::<U>()`. If either constraint is
14//! violated, the view creation returns [`CudaError::InvalidValue`].
15//! Allocations made via `cuMemAlloc`-backed buffers (e.g.
16//! [`DeviceBuffer::alloc`](crate::device_buffer::DeviceBuffer::alloc)) are
17//! always sufficiently aligned for any `U` no larger than the CUDA driver's
18//! allocation alignment guarantee, so this only rejects genuinely
19//! misaligned reinterpretations (e.g. viewing a byte-offset sub-buffer as a
20//! wider type).
21//!
22//! # Example
23//!
24//! ```rust,no_run
25//! # use oxicuda_memory::DeviceBuffer;
26//! # use oxicuda_memory::buffer_view::BufferView;
27//! let buf = DeviceBuffer::<f32>::alloc(256)?;
28//! // Reinterpret as u32 (same size, different type)
29//! let view: BufferView<'_, u32> = buf.view_as::<u32>()?;
30//! assert_eq!(view.len(), 256);
31//! # Ok::<(), oxicuda_driver::error::CudaError>(())
32//! ```
33
34use std::marker::PhantomData;
35
36use oxicuda_driver::error::{CudaError, CudaResult};
37use oxicuda_driver::ffi::CUdeviceptr;
38
39use crate::device_buffer::DeviceBuffer;
40
41// ---------------------------------------------------------------------------
42// BufferView<'a, U>
43// ---------------------------------------------------------------------------
44
45/// An immutable, type-reinterpreted view into a [`DeviceBuffer`].
46///
47/// This struct borrows the underlying device allocation and exposes it
48/// as a different element type `U`. No data is copied; only the
49/// pointer arithmetic changes.
50///
51/// The view is lifetime-bound to the original buffer, preventing use
52/// after the buffer is freed.
53pub struct BufferView<'a, U: Copy> {
54    /// Device pointer to the start of the buffer.
55    ptr: CUdeviceptr,
56    /// Number of `U` elements in the reinterpreted view.
57    len: usize,
58    /// Ties the lifetime to the parent buffer.
59    _phantom: PhantomData<&'a U>,
60}
61
62impl<U: Copy> BufferView<'_, U> {
63    /// Returns the number of `U` elements in this view.
64    #[inline]
65    pub fn len(&self) -> usize {
66        self.len
67    }
68
69    /// Returns `true` if the view contains zero elements.
70    #[inline]
71    pub fn is_empty(&self) -> bool {
72        self.len == 0
73    }
74
75    /// Returns the total byte size of this view.
76    #[inline]
77    pub fn byte_size(&self) -> usize {
78        self.len * std::mem::size_of::<U>()
79    }
80
81    /// Returns the raw [`CUdeviceptr`] for this view.
82    #[inline]
83    pub fn as_device_ptr(&self) -> CUdeviceptr {
84        self.ptr
85    }
86}
87
88impl<U: Copy> std::fmt::Debug for BufferView<'_, U> {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.debug_struct("BufferView")
91            .field("ptr", &self.ptr)
92            .field("len", &self.len)
93            .field("elem_size", &std::mem::size_of::<U>())
94            .finish()
95    }
96}
97
98// ---------------------------------------------------------------------------
99// BufferViewMut<'a, U>
100// ---------------------------------------------------------------------------
101
102/// A mutable, type-reinterpreted view into a [`DeviceBuffer`].
103///
104/// Like [`BufferView`] but allows mutable operations (e.g., passing
105/// to a kernel that writes through this reinterpreted pointer).
106pub struct BufferViewMut<'a, U: Copy> {
107    /// Device pointer to the start of the buffer.
108    ptr: CUdeviceptr,
109    /// Number of `U` elements in the reinterpreted view.
110    len: usize,
111    /// Ties the lifetime to the parent buffer (mutable borrow).
112    _phantom: PhantomData<&'a mut U>,
113}
114
115impl<U: Copy> BufferViewMut<'_, U> {
116    /// Returns the number of `U` elements in this view.
117    #[inline]
118    pub fn len(&self) -> usize {
119        self.len
120    }
121
122    /// Returns `true` if the view contains zero elements.
123    #[inline]
124    pub fn is_empty(&self) -> bool {
125        self.len == 0
126    }
127
128    /// Returns the total byte size of this view.
129    #[inline]
130    pub fn byte_size(&self) -> usize {
131        self.len * std::mem::size_of::<U>()
132    }
133
134    /// Returns the raw [`CUdeviceptr`] for this view.
135    #[inline]
136    pub fn as_device_ptr(&self) -> CUdeviceptr {
137        self.ptr
138    }
139}
140
141impl<U: Copy> std::fmt::Debug for BufferViewMut<'_, U> {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        f.debug_struct("BufferViewMut")
144            .field("ptr", &self.ptr)
145            .field("len", &self.len)
146            .field("elem_size", &std::mem::size_of::<U>())
147            .finish()
148    }
149}
150
151// ---------------------------------------------------------------------------
152// DeviceBuffer extensions
153// ---------------------------------------------------------------------------
154
155impl<T: Copy> DeviceBuffer<T> {
156    /// Reinterprets this buffer as a different element type `U` (immutable).
157    ///
158    /// The total byte size of the buffer must be evenly divisible by
159    /// `size_of::<U>()`. The resulting view has `byte_size / size_of::<U>()`
160    /// elements.
161    ///
162    /// # Errors
163    ///
164    /// Returns [`CudaError::InvalidValue`] if:
165    /// - `size_of::<U>()` is zero (ZST).
166    /// - The buffer's byte size is not divisible by `size_of::<U>()`.
167    /// - The buffer's device pointer is not aligned to `align_of::<U>()`.
168    pub fn view_as<U: Copy>(&self) -> CudaResult<BufferView<'_, U>> {
169        let u_size = std::mem::size_of::<U>();
170        if u_size == 0 {
171            return Err(CudaError::InvalidValue);
172        }
173        let byte_size = self.byte_size();
174        if byte_size % u_size != 0 {
175            return Err(CudaError::InvalidValue);
176        }
177        let u_align = std::mem::align_of::<U>() as u64;
178        if self.as_device_ptr() % u_align != 0 {
179            return Err(CudaError::InvalidValue);
180        }
181        Ok(BufferView {
182            ptr: self.as_device_ptr(),
183            len: byte_size / u_size,
184            _phantom: PhantomData,
185        })
186    }
187
188    /// Reinterprets this buffer as a different element type `U` (mutable).
189    ///
190    /// The total byte size of the buffer must be evenly divisible by
191    /// `size_of::<U>()`. The resulting view has `byte_size / size_of::<U>()`
192    /// elements.
193    ///
194    /// # Errors
195    ///
196    /// Returns [`CudaError::InvalidValue`] if:
197    /// - `size_of::<U>()` is zero (ZST).
198    /// - The buffer's byte size is not divisible by `size_of::<U>()`.
199    /// - The buffer's device pointer is not aligned to `align_of::<U>()`.
200    pub fn view_as_mut<U: Copy>(&mut self) -> CudaResult<BufferViewMut<'_, U>> {
201        let u_size = std::mem::size_of::<U>();
202        if u_size == 0 {
203            return Err(CudaError::InvalidValue);
204        }
205        let byte_size = self.byte_size();
206        if byte_size % u_size != 0 {
207            return Err(CudaError::InvalidValue);
208        }
209        let u_align = std::mem::align_of::<U>() as u64;
210        if self.as_device_ptr() % u_align != 0 {
211            return Err(CudaError::InvalidValue);
212        }
213        Ok(BufferViewMut {
214            ptr: self.as_device_ptr(),
215            len: byte_size / u_size,
216            _phantom: PhantomData,
217        })
218    }
219}
220
221// ---------------------------------------------------------------------------
222// Tests
223// ---------------------------------------------------------------------------
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn buffer_view_debug() {
231        let view: BufferView<'_, u32> = BufferView {
232            ptr: 0x1000,
233            len: 64,
234            _phantom: PhantomData,
235        };
236        let dbg = format!("{view:?}");
237        assert!(dbg.contains("BufferView"));
238        assert!(dbg.contains("64"));
239    }
240
241    #[test]
242    fn buffer_view_mut_debug() {
243        let view: BufferViewMut<'_, f32> = BufferViewMut {
244            ptr: 0x2000,
245            len: 128,
246            _phantom: PhantomData,
247        };
248        let dbg = format!("{view:?}");
249        assert!(dbg.contains("BufferViewMut"));
250        assert!(dbg.contains("128"));
251    }
252
253    #[test]
254    fn buffer_view_len_and_byte_size() {
255        let view: BufferView<'_, u64> = BufferView {
256            ptr: 0x3000,
257            len: 32,
258            _phantom: PhantomData,
259        };
260        assert_eq!(view.len(), 32);
261        assert_eq!(view.byte_size(), 32 * 8);
262        assert!(!view.is_empty());
263        assert_eq!(view.as_device_ptr(), 0x3000);
264    }
265
266    #[test]
267    fn buffer_view_mut_len_and_byte_size() {
268        let view: BufferViewMut<'_, u16> = BufferViewMut {
269            ptr: 0x4000,
270            len: 100,
271            _phantom: PhantomData,
272        };
273        assert_eq!(view.len(), 100);
274        assert_eq!(view.byte_size(), 200);
275        assert!(!view.is_empty());
276        assert_eq!(view.as_device_ptr(), 0x4000);
277    }
278
279    #[test]
280    fn buffer_view_empty() {
281        let view: BufferView<'_, f64> = BufferView {
282            ptr: 0,
283            len: 0,
284            _phantom: PhantomData,
285        };
286        assert!(view.is_empty());
287        assert_eq!(view.byte_size(), 0);
288    }
289
290    #[test]
291    fn view_as_signature_compiles() {
292        let _: fn(&DeviceBuffer<f32>) -> CudaResult<BufferView<'_, u32>> = DeviceBuffer::view_as;
293    }
294
295    #[test]
296    fn view_as_mut_signature_compiles() {
297        let _: fn(&mut DeviceBuffer<f32>) -> CudaResult<BufferViewMut<'_, u32>> =
298            DeviceBuffer::view_as_mut;
299    }
300
301    // -- Alignment validation (F104) -----------------------------------------
302    //
303    // These use `DeviceBuffer::from_raw`, which constructs a non-owning view
304    // over an arbitrary pointer value without touching the driver or
305    // dereferencing the pointer, so they exercise the pure pointer-arithmetic
306    // validation in `view_as`/`view_as_mut` without requiring a GPU.
307
308    #[test]
309    fn view_as_rejects_misaligned_pointer() {
310        // Byte size (8) divides evenly by `align_of::<u32>()` (4), but the
311        // pointer itself (0x1001) is not 4-byte aligned.
312        let buf = unsafe { DeviceBuffer::<u8>::from_raw(0x1001, 8) };
313        let result = buf.view_as::<u32>();
314        assert_eq!(result.err(), Some(CudaError::InvalidValue));
315    }
316
317    #[test]
318    fn view_as_accepts_aligned_pointer() {
319        let buf = unsafe { DeviceBuffer::<u8>::from_raw(0x1000, 8) };
320        let view = buf.view_as::<u32>().expect("aligned view should succeed");
321        assert_eq!(view.len(), 2);
322    }
323
324    #[test]
325    fn view_as_mut_rejects_misaligned_pointer() {
326        let mut buf = unsafe { DeviceBuffer::<u8>::from_raw(0x1002, 8) };
327        let result = buf.view_as_mut::<u64>();
328        assert_eq!(result.err(), Some(CudaError::InvalidValue));
329    }
330
331    #[test]
332    fn view_as_zero_len_null_pointer_is_aligned() {
333        // A zero-length view from a null pointer must still pass the
334        // alignment check (0 % align == 0 for any nonzero align).
335        let buf = unsafe { DeviceBuffer::<u8>::from_raw(0, 0) };
336        let view = buf
337            .view_as::<u64>()
338            .expect("null zero-length view should pass alignment check");
339        assert_eq!(view.len(), 0);
340    }
341}