Skip to main content

hyperlight_common/virtq/
buffer.rs

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