Skip to main content

volas_core/
buffer.rs

1//! [`Buffer<T>`]: a typed, contiguous value buffer that is either **owned**
2//! (`Arc<Vec<T>>`, the volas-computed case) or **borrows foreign memory**
3//! zero-copy (e.g. an Arrow / NumPy buffer kept alive by a guard).
4//!
5//! Reads go through a slice uniformly ([`Deref`] to `[T]`), so every kernel —
6//! which already takes `&[T]` — is unaffected by where the bytes live. Mutation is
7//! copy-on-write: a borrowed (or shared-owned) buffer materialises to a uniquely
8//! owned `Vec` on the first write ([`Buffer::make_mut`]). This is the foundation
9//! for zero-copy ingest, zero-copy slicing, and zero-copy export.
10
11use std::any::Any;
12use std::fmt;
13use std::ops::Deref;
14use std::ptr::NonNull;
15use std::sync::Arc;
16
17/// A typed contiguous buffer: owned or a zero-copy borrow of foreign memory.
18pub enum Buffer<T> {
19    /// volas-owned, `Arc`-shared (cheap clone, copy-on-write mutation).
20    Owned(Arc<Vec<T>>),
21    /// A zero-copy view of foreign memory (Arrow / NumPy). `guard` keeps the
22    /// foreign allocation alive for exactly as long as this buffer does.
23    Borrowed {
24        ptr: NonNull<T>,
25        len: usize,
26        guard: Arc<dyn Any + Send + Sync>,
27    },
28}
29
30// SAFETY: `Borrowed` is only constructed (via `from_foreign`) with a `Send + Sync`
31// guard that owns the pointed-to memory; the elements are `T`, so the buffer is
32// `Send`/`Sync` exactly when `T` is.
33unsafe impl<T: Send + Sync> Send for Buffer<T> {}
34unsafe impl<T: Send + Sync> Sync for Buffer<T> {}
35
36impl<T> Buffer<T> {
37    /// An owned buffer from a `Vec` (the volas-computed path).
38    #[inline]
39    pub fn from_vec(v: Vec<T>) -> Self {
40        Buffer::Owned(Arc::new(v))
41    }
42
43    /// A zero-copy borrow of foreign memory.
44    ///
45    /// # Safety
46    /// `ptr` must point to `len` initialised, contiguous `T` that stay valid and
47    /// immutable for as long as `guard` is alive; `guard` must own that allocation.
48    #[inline]
49    pub unsafe fn from_foreign(ptr: NonNull<T>, len: usize, guard: Arc<dyn Any + Send + Sync>) -> Self {
50        Buffer::Borrowed { ptr, len, guard }
51    }
52
53    #[inline]
54    pub fn len(&self) -> usize {
55        match self {
56            Buffer::Owned(a) => a.len(),
57            Buffer::Borrowed { len, .. } => *len,
58        }
59    }
60
61    #[inline]
62    pub fn is_empty(&self) -> bool {
63        self.len() == 0
64    }
65
66    #[inline]
67    pub fn as_slice(&self) -> &[T] {
68        match self {
69            Buffer::Owned(a) => a.as_slice(),
70            // SAFETY: the invariant of `from_foreign` (guard keeps `len` valid `T` alive).
71            Buffer::Borrowed { ptr, len, .. } => unsafe {
72                std::slice::from_raw_parts(ptr.as_ptr(), *len)
73            },
74        }
75    }
76
77    /// Pointer to the first element (valid for [`len`](Self::len) reads).
78    #[inline]
79    pub fn as_ptr(&self) -> *const T {
80        self.as_slice().as_ptr()
81    }
82}
83
84impl<T: Send + Sync + 'static> Buffer<T> {
85    /// An owner handle that keeps this buffer's allocation alive — the keep-alive a
86    /// zero-copy foreign export (Arrow / DLPack) hands the consumer alongside the raw
87    /// [`as_ptr`](Self::as_ptr) pointer, so the bytes outlive this `Buffer`. Cloning is
88    /// `O(1)` (an `Arc` bump) and exposes no volas-internal type to the export crate.
89    #[inline]
90    pub fn keepalive(&self) -> Arc<dyn Any + Send + Sync> {
91        match self {
92            Buffer::Owned(a) => a.clone(),
93            Buffer::Borrowed { guard, .. } => guard.clone(),
94        }
95    }
96}
97
98impl<T: Clone> Buffer<T> {
99    /// Mutable owned access, copy-on-write: materialises a borrowed buffer (or an
100    /// aliased owned one) to a uniquely-owned `Vec` first, then hands out `&mut`.
101    #[inline]
102    pub fn make_mut(&mut self) -> &mut Vec<T> {
103        if matches!(self, Buffer::Borrowed { .. }) {
104            let owned = self.as_slice().to_vec();
105            *self = Buffer::Owned(Arc::new(owned));
106        }
107        match self {
108            Buffer::Owned(a) => Arc::make_mut(a),
109            Buffer::Borrowed { .. } => unreachable!("materialised to Owned above"), // LCOV_EXCL_LINE
110        }
111    }
112
113    /// Consume into an owned `Vec` — no copy when uniquely owned, else one copy.
114    #[inline]
115    pub fn into_vec(self) -> Vec<T> {
116        match self {
117            Buffer::Owned(a) => Arc::try_unwrap(a).unwrap_or_else(|a| (*a).clone()),
118            Buffer::Borrowed { .. } => self.as_slice().to_vec(),
119        }
120    }
121}
122
123impl<T> Deref for Buffer<T> {
124    type Target = [T];
125    #[inline]
126    fn deref(&self) -> &[T] {
127        self.as_slice()
128    }
129}
130
131impl<T: Clone> Clone for Buffer<T> {
132    #[inline]
133    fn clone(&self) -> Self {
134        match self {
135            Buffer::Owned(a) => Buffer::Owned(Arc::clone(a)),
136            Buffer::Borrowed { ptr, len, guard } => Buffer::Borrowed {
137                ptr: *ptr,
138                len: *len,
139                guard: Arc::clone(guard),
140            },
141        }
142    }
143}
144
145impl<T: PartialEq> PartialEq for Buffer<T> {
146    #[inline]
147    fn eq(&self, other: &Self) -> bool {
148        self.as_slice() == other.as_slice()
149    }
150}
151
152impl<T: fmt::Debug> fmt::Debug for Buffer<T> {
153    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154        f.debug_list().entries(self.as_slice().iter()).finish()
155    }
156}
157
158impl<T> From<Vec<T>> for Buffer<T> {
159    #[inline]
160    fn from(v: Vec<T>) -> Self {
161        Buffer::from_vec(v)
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    /// A `Borrowed` buffer that points into `v`'s data, with `v` (as an `Arc`) as
170    /// the guard keeping it alive — the shape an Arrow import produces.
171    fn borrowed(v: Vec<f64>) -> Buffer<f64> {
172        let arc = Arc::new(v);
173        let ptr = NonNull::new(arc.as_slice().as_ptr() as *mut f64).unwrap();
174        let len = arc.len();
175        // SAFETY: `arc` owns the `len` f64 and is moved in as the guard.
176        unsafe { Buffer::from_foreign(ptr, len, arc) }
177    }
178
179    #[test]
180    fn owned_basics() {
181        let b = Buffer::from_vec(vec![1.0, 2.0, 3.0]);
182        assert_eq!(b.len(), 3);
183        assert!(!b.is_empty());
184        assert_eq!(b.as_slice(), &[1.0, 2.0, 3.0]);
185        assert_eq!(&*b, &[1.0, 2.0, 3.0]); // Deref
186        assert!(Buffer::<f64>::from_vec(vec![]).is_empty());
187        assert_eq!(Buffer::from(vec![1.0]).into_vec(), vec![1.0]); // From + into_vec (no copy)
188        assert_eq!(format!("{:?}", b), "[1.0, 2.0, 3.0]"); // Debug
189        assert_eq!(b, b.clone()); // Clone + PartialEq (Owned)
190    }
191
192    #[test]
193    fn borrowed_reads_zero_copy() {
194        let b = borrowed(vec![1.0, 2.0, 3.0]);
195        assert_eq!(b.len(), 3);
196        assert!(!b.is_empty());
197        assert_eq!(b.as_slice(), &[1.0, 2.0, 3.0]);
198        let c = b.clone(); // Clone (Borrowed): shares the guard, no copy
199        assert_eq!(c.as_slice(), &[1.0, 2.0, 3.0]);
200        assert_eq!(b, c); // PartialEq across two borrowed views
201        assert_eq!(format!("{:?}", b), "[1.0, 2.0, 3.0]");
202    }
203
204    #[test]
205    fn borrowed_make_mut_materialises_cow() {
206        let mut b = borrowed(vec![1.0, 2.0]);
207        assert!(matches!(b, Buffer::Borrowed { .. }));
208        b.make_mut().push(3.0); // copy-on-write: becomes Owned
209        assert!(matches!(b, Buffer::Owned(_)));
210        assert_eq!(b.as_slice(), &[1.0, 2.0, 3.0]);
211        // an owned buffer's make_mut grows in place
212        b.make_mut().push(4.0);
213        assert_eq!(b.as_slice(), &[1.0, 2.0, 3.0, 4.0]);
214    }
215
216    #[test]
217    fn borrowed_into_vec_copies() {
218        assert_eq!(borrowed(vec![7.0, 8.0]).into_vec(), vec![7.0, 8.0]);
219        // owned-but-shared into_vec clones (try_unwrap fails)
220        let a = Buffer::from_vec(vec![5.0]);
221        let _alias = a.clone();
222        assert_eq!(a.into_vec(), vec![5.0]);
223    }
224}