Skip to content

Commit 1562354

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

File tree

4 files changed

+176
-1
lines changed

4 files changed

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

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)