Skip to content

Commit 3b33f5e

Browse files
committed
feat(virtq): cleanup send + sync bounds
Signed-off-by: Tomasz Andrzejak <andreiltd@gmail.com>
1 parent df5491f commit 3b33f5e

13 files changed

Lines changed: 360 additions & 361 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/hyperlight_common/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ workspace = true
1717
[dependencies]
1818
arbitrary = {version = "1.4.2", optional = true, features = ["derive"]}
1919
anyhow = { version = "1.0.102", default-features = false }
20-
atomic_refcell = "0.1.13"
2120
bitflags = "2.10.0"
2221
bytemuck = { version = "1.24", features = ["derive"] }
2322
bytes = { version = "1", default-features = false }

src/hyperlight_common/src/virtq/access.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ limitations under the License.
2020
//! required by the virtqueue implementation. This allows the virtqueue code to
2121
//! work with different memory backends e.g. Host vs Guest.
2222
23+
use alloc::sync::Arc;
24+
2325
use bytemuck::Pod;
2426

2527
/// Backend-provided memory access for virtqueue.
@@ -134,3 +136,32 @@ pub trait MemOps {
134136
Ok(())
135137
}
136138
}
139+
140+
impl<T: MemOps> MemOps for Arc<T> {
141+
type Error = T::Error;
142+
143+
fn read(&self, addr: u64, dst: &mut [u8]) -> Result<usize, Self::Error> {
144+
(**self).read(addr, dst)
145+
}
146+
147+
fn write(&self, addr: u64, src: &[u8]) -> Result<usize, Self::Error> {
148+
(**self).write(addr, src)
149+
}
150+
151+
fn load_acquire(&self, addr: u64) -> Result<u16, Self::Error> {
152+
(**self).load_acquire(addr)
153+
}
154+
155+
fn store_release(&self, addr: u64, val: u16) -> Result<(), Self::Error> {
156+
(**self).store_release(addr, val)
157+
}
158+
159+
unsafe fn as_slice(&self, addr: u64, len: usize) -> Result<&[u8], Self::Error> {
160+
unsafe { (**self).as_slice(addr, len) }
161+
}
162+
163+
#[allow(clippy::mut_from_ref)]
164+
unsafe fn as_mut_slice(&self, addr: u64, len: usize) -> Result<&mut [u8], Self::Error> {
165+
unsafe { (**self).as_mut_slice(addr, len) }
166+
}
167+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
Copyright 2026 The Hyperlight Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
//! Buffer allocation traits and shared types for virtqueue buffer management.
18+
19+
use alloc::rc::Rc;
20+
use alloc::sync::Arc;
21+
22+
use thiserror::Error;
23+
24+
use super::access::MemOps;
25+
26+
#[derive(Debug, Error, Copy, Clone)]
27+
pub enum AllocError {
28+
#[error("Invalid region addr {0}")]
29+
InvalidAlign(u64),
30+
#[error("Invalid free addr {0} and size {1}")]
31+
InvalidFree(u64, usize),
32+
#[error("Invalid argument")]
33+
InvalidArg,
34+
#[error("Empty region")]
35+
EmptyRegion,
36+
#[error("Out of memory")]
37+
OutOfMemory,
38+
#[error("Overflow")]
39+
Overflow,
40+
}
41+
42+
/// Allocation result
43+
#[derive(Debug, Clone, Copy)]
44+
pub struct Allocation {
45+
/// Starting address of the allocation
46+
pub addr: u64,
47+
/// Length of the allocation in bytes rounded up to slab size
48+
pub len: usize,
49+
}
50+
51+
/// Trait for buffer providers.
52+
pub trait BufferProvider {
53+
/// Allocate at least `len` bytes.
54+
fn alloc(&self, len: usize) -> Result<Allocation, AllocError>;
55+
56+
/// Free a previously allocated block.
57+
fn dealloc(&self, alloc: Allocation) -> Result<(), AllocError>;
58+
59+
/// Resize by trying in-place grow; otherwise reserve a new block and free old.
60+
fn resize(&self, old_alloc: Allocation, new_len: usize) -> Result<Allocation, AllocError>;
61+
62+
/// Reset the pool to initial state.
63+
fn reset(&self) {}
64+
}
65+
66+
impl<T: BufferProvider> BufferProvider for Rc<T> {
67+
fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
68+
(**self).alloc(len)
69+
}
70+
fn dealloc(&self, alloc: Allocation) -> Result<(), AllocError> {
71+
(**self).dealloc(alloc)
72+
}
73+
fn resize(&self, old_alloc: Allocation, new_len: usize) -> Result<Allocation, AllocError> {
74+
(**self).resize(old_alloc, new_len)
75+
}
76+
fn reset(&self) {
77+
(**self).reset()
78+
}
79+
}
80+
81+
impl<T: BufferProvider> BufferProvider for Arc<T> {
82+
fn alloc(&self, len: usize) -> Result<Allocation, AllocError> {
83+
(**self).alloc(len)
84+
}
85+
fn dealloc(&self, alloc: Allocation) -> Result<(), AllocError> {
86+
(**self).dealloc(alloc)
87+
}
88+
fn resize(&self, old_alloc: Allocation, new_len: usize) -> Result<Allocation, AllocError> {
89+
(**self).resize(old_alloc, new_len)
90+
}
91+
fn reset(&self) {
92+
(**self).reset()
93+
}
94+
}
95+
96+
/// The owner of a mapped buffer, ensuring its lifetime.
97+
///
98+
/// Holds a pool allocation and provides direct access to the underlying
99+
/// shared memory via [`MemOps::as_slice`]. Implements `AsRef<[u8]>` so it
100+
/// can be used with [`Bytes::from_owner`](bytes::Bytes::from_owner) for
101+
/// zero-copy `Bytes` backed by shared memory.
102+
///
103+
/// When dropped, the allocation is returned to the pool.
104+
#[derive(Debug, Clone)]
105+
pub struct BufferOwner<P: BufferProvider, M: MemOps> {
106+
pub(crate) pool: P,
107+
pub(crate) mem: M,
108+
pub(crate) alloc: Allocation,
109+
pub(crate) written: usize,
110+
}
111+
112+
impl<P: BufferProvider, M: MemOps> Drop for BufferOwner<P, M> {
113+
fn drop(&mut self) {
114+
let _ = self.pool.dealloc(self.alloc);
115+
}
116+
}
117+
118+
impl<P: BufferProvider, M: MemOps> AsRef<[u8]> for BufferOwner<P, M> {
119+
fn as_ref(&self) -> &[u8] {
120+
let len = self.written.min(self.alloc.len);
121+
// Safety: BufferOwner keeps both the pool allocation and the M
122+
// alive, so the memory region is valid. Protocol-level descriptor
123+
// ownership transfer guarantees no concurrent writes.
124+
match unsafe { self.mem.as_slice(self.alloc.addr, len) } {
125+
Ok(slice) => slice,
126+
Err(_) => &[],
127+
}
128+
}
129+
}
130+
131+
/// A guard that runs a cleanup function when dropped, unless dismissed.
132+
pub struct AllocGuard<F: FnOnce(Allocation)>(Option<(Allocation, F)>);
133+
134+
impl<F: FnOnce(Allocation)> AllocGuard<F> {
135+
pub fn new(alloc: Allocation, cleanup: F) -> Self {
136+
Self(Some((alloc, cleanup)))
137+
}
138+
139+
pub fn release(mut self) -> Allocation {
140+
self.0.take().unwrap().0
141+
}
142+
}
143+
144+
impl<F: FnOnce(Allocation)> core::ops::Deref for AllocGuard<F> {
145+
type Target = Allocation;
146+
147+
fn deref(&self) -> &Allocation {
148+
&self.0.as_ref().unwrap().0
149+
}
150+
}
151+
152+
impl<F: FnOnce(Allocation)> Drop for AllocGuard<F> {
153+
fn drop(&mut self) {
154+
if let Some((alloc, cleanup)) = self.0.take() {
155+
cleanup(alloc)
156+
}
157+
}
158+
}

src/hyperlight_common/src/virtq/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,19 @@ limitations under the License.
151151
//! ```
152152
153153
mod access;
154+
mod buffer;
154155
mod consumer;
155156
mod desc;
156157
mod event;
157158
pub mod msg;
158159
mod pool;
159160
mod producer;
160-
pub mod recycle_pool;
161161
mod ring;
162162

163163
use core::num::NonZeroU16;
164164

165165
pub use access::*;
166+
pub use buffer::*;
166167
pub use consumer::*;
167168
pub use desc::*;
168169
pub use event::*;
@@ -440,8 +441,8 @@ pub(crate) mod test_utils {
440441
}
441442
}
442443

443-
type TestProducer = VirtqProducer<Arc<TestMem>, TestNotifier, TestPool>;
444-
type TestConsumer = VirtqConsumer<Arc<TestMem>, TestNotifier>;
444+
type TestProducer = VirtqProducer<TestMem, TestNotifier, TestPool>;
445+
type TestConsumer = VirtqConsumer<TestMem, TestNotifier>;
445446

446447
/// Create test infrastructure: a producer, consumer, and notifier backed
447448
/// by the supplied [`OwnedRing`].
@@ -474,7 +475,7 @@ mod tests {
474475

475476
/// Helper: build and submit an entry+completion chain using the chain() builder.
476477
fn send_readwrite(
477-
producer: &mut VirtqProducer<Arc<TestMem>, TestNotifier, TestPool>,
478+
producer: &mut VirtqProducer<TestMem, TestNotifier, TestPool>,
478479
entry_data: &[u8],
479480
cqe_cap: usize,
480481
) -> Token {
@@ -957,7 +958,7 @@ mod fuzz {
957958
}
958959
}
959960

960-
impl MemOps for Arc<LoomMem> {
961+
impl MemOps for LoomMem {
961962
type Error = MemErr;
962963

963964
fn read(&self, addr: u64, dst: &mut [u8]) -> Result<usize, Self::Error> {

0 commit comments

Comments
 (0)