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}