Skip to main content

hyperlight_common/virtq/
buffer.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 The Hyperlight Authors.
3
4//! Buffer allocation traits and shared types for virtqueue buffer management.
5
6use alloc::rc::Rc;
7use alloc::sync::Arc;
8use alloc::vec::Vec;
9
10use bytes::{Buf, Bytes};
11use smallvec::{SmallVec, smallvec};
12use thiserror::Error;
13
14use super::access::MemOps;
15
16#[derive(Debug, Error, Copy, Clone)]
17pub enum AllocError {
18    #[error("Invalid region addr {0}")]
19    InvalidAlign(u64),
20    #[error("Invalid free addr {0} and size {1}")]
21    InvalidFree(u64, usize),
22    #[error("Invalid argument")]
23    InvalidArg,
24    #[error("Empty region")]
25    EmptyRegion,
26    #[error("No space available")]
27    NoSpace,
28    #[error("Requested size exceeds pool capacity")]
29    OutOfMemory,
30    #[error("Overflow")]
31    Overflow,
32}
33
34/// Allocation result
35#[derive(Debug, Clone, Copy)]
36pub struct Allocation {
37    /// Starting address of the allocation
38    pub addr: u64,
39    /// Capacity of the allocation in bytes, rounded up to the allocator's slot size.
40    pub len: usize,
41}
42
43/// Trait for buffer providers.
44pub trait BufferProvider {
45    /// Preferred maximum size of one allocation segment.
46    fn max_alloc_len(&self) -> usize {
47        usize::MAX
48    }
49
50    /// Allocate one buffer that can hold at least `len` bytes.
51    fn alloc(&self, len: usize) -> Result<Allocation, AllocError>;
52
53    /// Free a previously allocated segment by start address.
54    fn dealloc(&self, addr: u64) -> Result<(), AllocError>;
55
56    /// Reset the pool to initial state.
57    fn reset(&self) {}
58
59    /// Allocate scatter/gather segments for a logical payload of `total_len` bytes.
60    fn alloc_sg(&self, total_len: usize) -> Result<SmallVec<[Allocation; 4]>, AllocError> {
61        if total_len == 0 {
62            return Err(AllocError::InvalidArg);
63        }
64
65        let seg_cap = self.max_alloc_len();
66        if seg_cap == 0 {
67            return Err(AllocError::InvalidArg);
68        }
69
70        let mut rem = total_len;
71        let mut sgs = SmallVec::<[Allocation; 4]>::new();
72
73        while rem > 0 {
74            let len = rem.min(seg_cap);
75            match self.alloc(len) {
76                Ok(alloc) => {
77                    sgs.push(alloc);
78                    rem -= len;
79                }
80                Err(err) => {
81                    for sg in sgs {
82                        let _res = self.dealloc(sg.addr);
83                        debug_assert!(_res.is_ok(), "dealloc failed: {_res:?}");
84                    }
85                    return Err(err);
86                }
87            }
88        }
89
90        Ok(sgs)
91    }
92}
93
94impl<T: BufferProvider> BufferProvider for Rc<T> {
95    fn max_alloc_len(&self) -> usize {
96        (**self).max_alloc_len()
97    }
98    fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
99        (**self).alloc(len)
100    }
101    fn dealloc(&self, addr: u64) -> Result<(), AllocError> {
102        (**self).dealloc(addr)
103    }
104    fn reset(&self) {
105        (**self).reset()
106    }
107    fn alloc_sg(&self, total_len: usize) -> Result<SmallVec<[Allocation; 4]>, AllocError> {
108        (**self).alloc_sg(total_len)
109    }
110}
111
112impl<T: BufferProvider> BufferProvider for Arc<T> {
113    fn max_alloc_len(&self) -> usize {
114        (**self).max_alloc_len()
115    }
116    fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
117        (**self).alloc(len)
118    }
119    fn dealloc(&self, addr: u64) -> Result<(), AllocError> {
120        (**self).dealloc(addr)
121    }
122    fn reset(&self) {
123        (**self).reset()
124    }
125    fn alloc_sg(&self, total_len: usize) -> Result<SmallVec<[Allocation; 4]>, AllocError> {
126        (**self).alloc_sg(total_len)
127    }
128}
129
130/// Ordered byte segments that make up one virtqueue payload.
131///
132/// This is the high-level counterpart to the descriptor-oriented
133/// [`BufferChain`](super::BufferChain).
134#[derive(Debug, Clone, Default)]
135pub struct Segments(SmallVec<[Bytes; 4]>);
136
137impl Segments {
138    /// Build a segmented payload from ordered byte segments.
139    pub fn new(segments: impl IntoIterator<Item = Bytes>) -> Self {
140        Self(segments.into_iter().collect())
141    }
142
143    /// Build a single-segment payload.
144    pub fn single(segment: Bytes) -> Self {
145        Self(smallvec![segment])
146    }
147
148    pub(crate) fn from_smallvec(segments: SmallVec<[Bytes; 4]>) -> Self {
149        Self(segments)
150    }
151
152    /// Total payload length across all segments.
153    pub fn len(&self) -> usize {
154        self.0.iter().map(Bytes::len).sum()
155    }
156
157    /// Whether the payload contains zero bytes.
158    pub fn is_empty(&self) -> bool {
159        self.len() == 0
160    }
161
162    /// Number of byte segments.
163    pub fn segment_count(&self) -> usize {
164        self.0.len()
165    }
166
167    /// Borrow all segments.
168    pub fn as_slice(&self) -> &[Bytes] {
169        &self.0
170    }
171
172    /// Iterate over segments.
173    pub fn iter(&self) -> impl Iterator<Item = &Bytes> {
174        self.0.iter()
175    }
176
177    /// Borrow this payload as a [`Buf`] cursor.
178    pub fn as_buf(&self) -> SegmentsBuf<'_> {
179        SegmentsBuf::new(&self.0, self.len())
180    }
181
182    /// Return this payload as contiguous bytes.
183    ///
184    /// This is O(1) for zero or one segment, and allocates/copies for multiple
185    /// segments.
186    pub fn to_bytes(&self) -> Bytes {
187        match self.0.as_slice() {
188            [] => Bytes::new(),
189            [segment] => segment.clone(),
190            _ => self.collect(&self.0, self.len()),
191        }
192    }
193
194    /// Consume this payload and return contiguous bytes.
195    ///
196    /// This is O(1) for zero or one segment, and allocates/copies for multiple
197    /// segments.
198    pub fn into_bytes(mut self) -> Bytes {
199        match self.0.len() {
200            0 => Bytes::new(),
201            1 => self.0.pop().unwrap_or_default(),
202            _ => self.collect(&self.0, self.len()),
203        }
204    }
205
206    fn collect(&self, sgs: &[Bytes], len: usize) -> Bytes {
207        let mut out = Vec::with_capacity(len);
208        out.extend(sgs.iter().flat_map(|seg| seg.iter().copied()));
209        Bytes::from(out)
210    }
211}
212
213/// Borrowed [`Buf`] cursor over [`Segments`].
214///
215/// Advancing the cursor does not mutate the underlying [`Segments`].
216#[derive(Debug, Clone)]
217pub struct SegmentsBuf<'a> {
218    segments: &'a [Bytes],
219    index: usize,
220    offset: usize,
221    remaining: usize,
222}
223
224impl<'a> SegmentsBuf<'a> {
225    fn new(segments: &'a [Bytes], len: usize) -> Self {
226        let mut this = Self {
227            segments,
228            index: 0,
229            offset: 0,
230            remaining: len,
231        };
232
233        this.skip_empty_segments();
234        this
235    }
236
237    fn skip_empty_segments(&mut self) {
238        while self.index < self.segments.len() && self.offset >= self.segments[self.index].len() {
239            self.index += 1;
240            self.offset = 0;
241        }
242    }
243}
244
245impl Buf for SegmentsBuf<'_> {
246    fn remaining(&self) -> usize {
247        self.remaining
248    }
249
250    fn chunk(&self) -> &[u8] {
251        if self.remaining == 0 {
252            return &[];
253        }
254
255        let segment = self.segments[self.index].as_ref();
256        &segment[self.offset..]
257    }
258
259    fn advance(&mut self, cnt: usize) {
260        assert!(cnt <= self.remaining, "cannot advance past remaining bytes");
261
262        self.remaining -= cnt;
263        let mut cnt = cnt;
264
265        while cnt > 0 {
266            let seg_rem = self.segments[self.index].len() - self.offset;
267            let n = seg_rem.min(cnt);
268            self.offset += n;
269            cnt -= n;
270            self.skip_empty_segments();
271        }
272
273        if self.remaining == 0 {
274            self.index = self.segments.len();
275            self.offset = 0;
276        }
277    }
278}
279
280/// The owner of a mapped buffer, ensuring its lifetime.
281///
282/// Holds an [`OwnedAlloc`] and provides direct access to the underlying
283/// shared memory via [`MemOps::as_slice`]. Implements `AsRef<[u8]>` so it
284/// can be used with [`Bytes::from_owner`](bytes::Bytes::from_owner) for
285/// zero-copy `Bytes` backed by shared memory.
286///
287/// When dropped, the allocation is returned to the pool.
288#[derive(Debug)]
289pub struct BufferOwner<P: BufferProvider, M: MemOps> {
290    pub(crate) mem: M,
291    pub(crate) alloc: OwnedAlloc<P>,
292    pub(crate) written: usize,
293}
294
295impl<P: BufferProvider, M: MemOps> AsRef<[u8]> for BufferOwner<P, M> {
296    fn as_ref(&self) -> &[u8] {
297        let alloc = self.alloc.allocation();
298        let len = self.written.min(alloc.len);
299        // Safety: BufferOwner keeps both the pool allocation and the M alive,
300        // so the memory region is valid.
301        match unsafe { self.mem.as_slice(alloc.addr, len) } {
302            Ok(slice) => slice,
303            Err(_) => {
304                debug_assert!(false, "BufferOwner direct slice failed");
305                &[]
306            }
307        }
308    }
309}
310
311/// Pool-owned allocation that is returned to the pool on drop.
312///
313/// Use [`into_raw`](Self::into_raw) to transfer ownership to a descriptor
314/// state that will deallocate the raw [`Allocation`] through another path.
315#[derive(Debug)]
316pub struct OwnedAlloc<P: BufferProvider> {
317    inner: Option<Inner<P>>,
318}
319
320#[derive(Debug)]
321struct Inner<P: BufferProvider> {
322    pool: P,
323    alloc: Allocation,
324}
325
326impl<P: BufferProvider> OwnedAlloc<P> {
327    /// Wrap an existing allocation with its owning pool.
328    pub fn new(pool: P, alloc: Allocation) -> Self {
329        Self {
330            inner: Some(Inner { pool, alloc }),
331        }
332    }
333
334    /// Allocate from `pool` and return an owning guard.
335    pub fn allocate(pool: P, len: usize) -> Result<Self, AllocError> {
336        let alloc = pool.alloc(len)?;
337        Ok(Self::new(pool, alloc))
338    }
339
340    /// The raw allocation currently owned by this guard.
341    // `inner` is `Some` for the whole lifetime of a live guard: it is only
342    // taken by `into_raw` which consumes `self` or on drop, so this access
343    // cannot fail.
344    #[allow(clippy::expect_used)]
345    pub fn allocation(&self) -> Allocation {
346        self.inner
347            .as_ref()
348            .map(|inner| inner.alloc)
349            .expect("OwnedAlloc::allocation called after ownership transfer")
350    }
351
352    /// Release ownership and return the raw allocation.
353    // `inner` is `Some` until ownership is released, and `into_raw` consumes
354    // `self`, so it can only ever observe `Some` here.
355    #[allow(clippy::expect_used)]
356    pub fn into_raw(mut self) -> Allocation {
357        self.inner
358            .take()
359            .map(|inner| inner.alloc)
360            .expect("OwnedAlloc::into_raw called after ownership transfer")
361    }
362}
363
364impl<P: BufferProvider> Drop for OwnedAlloc<P> {
365    fn drop(&mut self) {
366        if let Some(Inner { pool, alloc }) = self.inner.take() {
367            let result = pool.dealloc(alloc.addr);
368            debug_assert!(result.is_ok(), "OwnedAlloc drop dealloc failed: {result:?}");
369        }
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use bytes::Buf;
376
377    use super::*;
378
379    #[test]
380    fn segments_cursor_advances_across_segments() {
381        let segments = Segments::new([
382            Bytes::from_static(b"abc"),
383            Bytes::from_static(b"def"),
384            Bytes::from_static(b"ghi"),
385        ]);
386        let mut cursor = segments.as_buf();
387
388        assert_eq!(cursor.remaining(), 9);
389        assert_eq!(cursor.chunk(), b"abc");
390
391        cursor.advance(2);
392        assert_eq!(cursor.remaining(), 7);
393        assert_eq!(cursor.chunk(), b"c");
394
395        cursor.advance(1);
396        assert_eq!(cursor.chunk(), b"def");
397
398        cursor.advance(4);
399        assert_eq!(cursor.chunk(), b"hi");
400
401        cursor.advance(2);
402        assert_eq!(cursor.remaining(), 0);
403        assert_eq!(cursor.chunk(), b"");
404    }
405
406    #[test]
407    fn segments_cursor_skips_empty_segments() {
408        let segments = Segments::new([
409            Bytes::new(),
410            Bytes::from_static(b"ab"),
411            Bytes::new(),
412            Bytes::from_static(b"cd"),
413            Bytes::new(),
414        ]);
415        let mut cursor = segments.as_buf();
416
417        assert_eq!(cursor.remaining(), 4);
418        assert_eq!(cursor.chunk(), b"ab");
419
420        cursor.advance(2);
421        assert_eq!(cursor.remaining(), 2);
422        assert_eq!(cursor.chunk(), b"cd");
423
424        cursor.advance(2);
425        assert!(!cursor.has_remaining());
426        assert_eq!(cursor.chunk(), b"");
427    }
428
429    #[test]
430    fn segments_cursor_reads_split_header_without_collecting_all_segments() {
431        let segments = Segments::new([
432            Bytes::from_static(&[0x01, 0x02, 0x03]),
433            Bytes::from_static(&[0x04, 0x05]),
434            Bytes::from_static(&[0x06, 0x07, 0x08, 0xff]),
435        ]);
436        let mut cursor = segments.as_buf();
437        let mut header = [0u8; 8];
438
439        cursor.try_copy_to_slice(&mut header).unwrap();
440
441        assert_eq!(header, [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
442        assert_eq!(cursor.remaining(), 1);
443        assert_eq!(cursor.chunk(), &[0xff]);
444    }
445
446    #[test]
447    fn segments_cursor_copy_to_bytes_collects_only_requested_prefix() {
448        let segments = Segments::new([
449            Bytes::from_static(b"hello"),
450            Bytes::from_static(b" "),
451            Bytes::from_static(b"world"),
452        ]);
453        let mut cursor = segments.as_buf();
454
455        let prefix = cursor.copy_to_bytes(6);
456
457        assert_eq!(prefix.as_ref(), b"hello ");
458        assert_eq!(cursor.remaining(), 5);
459        assert_eq!(cursor.chunk(), b"world");
460    }
461
462    #[test]
463    fn segments_into_bytes_reuses_single_segment() {
464        let segment = Bytes::from(vec![1, 2, 3, 4]);
465        let ptr = segment.as_ptr();
466
467        let collected = Segments::single(segment).into_bytes();
468
469        assert_eq!(collected.as_ptr(), ptr);
470        assert_eq!(collected.as_ref(), &[1, 2, 3, 4]);
471    }
472}