Skip to content

Commit db81f22

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

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

src/hyperlight_common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ log = "0.4.26"
2121
tracing = { version = "0.1.41", optional = true }
2222
strum = {version = "0.27", default-features = false, features = ["derive"]}
2323
arbitrary = {version = "1.4.1", optional = true, features = ["derive"]}
24+
spin = "0.9.8"
2425

2526
[features]
2627
default = ["tracing"]

src/hyperlight_common/src/lib.rs

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

0 commit comments

Comments
 (0)