Skip to content

Commit e2ff7ed

Browse files
committed
[hyperlight_common] Add resource table abstraction
Signed-off-by: Lucy Menon <[email protected]>
1 parent dd55d85 commit e2ff7ed

File tree

4 files changed

+178
-1
lines changed

4 files changed

+178
-1
lines changed

src/hyperlight_common/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ anyhow = { version = "1.0.98", default-features = false }
2020
log = "0.4.27"
2121
tracing = { version = "0.1.41", optional = true }
2222
arbitrary = {version = "1.4.1", optional = true, features = ["derive"]}
23+
spin = "0.9.8"
2324

2425
[features]
2526
default = ["tracing"]
2627
fuzzing = ["dep:arbitrary"]
28+
std = []
2729

2830
[dev-dependencies]
2931
hyperlight-testing = { workspace = true }

src/hyperlight_common/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,6 @@ pub mod mem;
3939

4040
/// cbindgen:ignore
4141
pub mod outb;
42+
43+
/// cbindgen:ignore
44+
pub mod resource;
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/*
2+
Copyright 2025 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+
//! Shared operations around resources
18+
19+
// "Needless" lifetimes are useful for clarity
20+
#![allow(clippy::needless_lifetimes)]
21+
22+
use alloc::sync::Arc;
23+
24+
#[cfg(feature = "std")]
25+
extern crate std;
26+
use core::marker::{PhantomData, Send};
27+
use core::ops::Deref;
28+
#[cfg(feature = "std")]
29+
use std::sync::{RwLock, RwLockReadGuard};
30+
31+
#[cfg(not(feature = "std"))]
32+
use spin::{RwLock, RwLockReadGuard};
33+
34+
/// The semantics of component model resources are, pleasingly,
35+
/// roughly compatible with those of Rust references, so we would like
36+
/// to use the more-or-less directly in interfaces generated by
37+
/// hyperlight_component_macro. Less pleasingly, it's not terribly
38+
/// easy to show the semantic agreement statically.
39+
///
40+
/// In particular, if the host calls into the guest and gives it a
41+
/// borrow of a resource, reentrant host function calls that use that
42+
/// borrow need to be able to resolve the original reference and use
43+
/// it in an appropriately scoped manner, but it is not simple to do
44+
/// this, because the core Hyperlight machinery doesn't offer an easy
45+
/// way to augment the host's context for the span of time of a guest
46+
/// function call. This may be worth revisiting at some time, but in
47+
/// the meantime, it's easier to just do it dynamically.
48+
///
49+
/// # Safety
50+
/// Informally: this only creates SharedRead references, so having a
51+
/// bunch of them going at once is fine. Safe Rust in the host can't
52+
/// use any earlier borrows (potentially invalidating these) until
53+
/// borrow passed into [`ResourceEntry::lend`] has expired. Because
54+
/// that borrow outlives the [`LentResourceGuard`], it will not expire
55+
/// until that destructor is called. That destructor ensures that (a)
56+
/// there are no outstanding [`BorrowedResourceGuard`]s alive (since
57+
/// they would be holding the read side of the [`RwLock`] if they
58+
/// were), and that (b) the shared flag has been set to false, so
59+
/// [`ResourceEntry::borrow`] will never create another borrow
60+
pub enum ResourceEntry<T> {
61+
Empty,
62+
Owned(T),
63+
Borrowed(Arc<RwLock<bool>>, *const T),
64+
}
65+
unsafe impl<T: Send> Send for ResourceEntry<T> {}
66+
67+
pub struct LentResourceGuard<'a> {
68+
flag: Arc<RwLock<bool>>,
69+
already_revoked: bool,
70+
_phantom: core::marker::PhantomData<&'a mut ()>,
71+
}
72+
impl<'a> LentResourceGuard<'a> {
73+
pub fn revoke_nonblocking(&mut self) -> bool {
74+
#[cfg(feature = "std")]
75+
let Ok(mut flag) = self.flag.try_write() else {
76+
return false;
77+
};
78+
#[cfg(not(feature = "std"))]
79+
let Some(mut flag) = self.flag.try_write() else {
80+
return false;
81+
};
82+
*flag = false;
83+
self.already_revoked = true;
84+
true
85+
}
86+
}
87+
impl<'a> Drop for LentResourceGuard<'a> {
88+
fn drop(&mut self) {
89+
if !self.already_revoked {
90+
#[allow(unused_mut)] // it isn't actually unused
91+
let mut guard = self.flag.write();
92+
#[cfg(feature = "std")]
93+
// If a mutex that is just protecting us from our own
94+
// mistakes is poisoned, something is so seriously
95+
// wrong that dying is a sensible response.
96+
#[allow(clippy::unwrap_used)]
97+
{
98+
*guard.unwrap() = false;
99+
}
100+
#[cfg(not(feature = "std"))]
101+
{
102+
*guard = false;
103+
}
104+
}
105+
}
106+
}
107+
pub struct BorrowedResourceGuard<'a, T> {
108+
_flag: Option<RwLockReadGuard<'a, bool>>,
109+
reference: &'a T,
110+
}
111+
impl<'a, T> Deref for BorrowedResourceGuard<'a, T> {
112+
type Target = T;
113+
fn deref(&self) -> &T {
114+
self.reference
115+
}
116+
}
117+
impl<T> ResourceEntry<T> {
118+
pub fn give(x: T) -> ResourceEntry<T> {
119+
ResourceEntry::Owned(x)
120+
}
121+
pub fn lend<'a>(x: &'a T) -> (LentResourceGuard<'a>, ResourceEntry<T>) {
122+
let flag = Arc::new(RwLock::new(true));
123+
(
124+
LentResourceGuard {
125+
flag: flag.clone(),
126+
already_revoked: false,
127+
_phantom: PhantomData {},
128+
},
129+
ResourceEntry::Borrowed(flag, x as *const T),
130+
)
131+
}
132+
pub fn borrow<'a>(&'a self) -> Option<BorrowedResourceGuard<'a, T>> {
133+
match self {
134+
ResourceEntry::Empty => None,
135+
ResourceEntry::Owned(t) => Some(BorrowedResourceGuard {
136+
_flag: None,
137+
reference: t,
138+
}),
139+
ResourceEntry::Borrowed(flag, t) => {
140+
let guard = flag.read();
141+
// If a mutex that is just protecting us from our own
142+
// mistakes is poisoned, something is so seriously
143+
// wrong that dying is a sensible response.
144+
#[allow(clippy::unwrap_used)]
145+
let flag = {
146+
#[cfg(feature = "std")]
147+
{
148+
guard.unwrap()
149+
}
150+
#[cfg(not(feature = "std"))]
151+
{
152+
guard
153+
}
154+
};
155+
if *flag {
156+
Some(BorrowedResourceGuard {
157+
_flag: Some(flag),
158+
reference: unsafe { &**t },
159+
})
160+
} else {
161+
None
162+
}
163+
}
164+
}
165+
}
166+
pub fn take(&mut self) -> Option<T> {
167+
match core::mem::replace(self, ResourceEntry::Empty) {
168+
ResourceEntry::Owned(t) => Some(t),
169+
_ => None,
170+
}
171+
}
172+
}

src/hyperlight_host/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ log = "0.4.27"
3333
tracing = { version = "0.1.41", features = ["log"] }
3434
tracing-log = "0.2.0"
3535
tracing-core = "0.1.33"
36-
hyperlight-common = { workspace = true, default-features = true }
36+
hyperlight-common = { workspace = true, default-features = true, features = [ "std" ] }
3737
vmm-sys-util = "0.14.0"
3838
crossbeam = "0.8.0"
3939
crossbeam-channel = "0.5.15"

0 commit comments

Comments
 (0)