hyperlight_common/resource.rs
1/*
2Copyright 2025 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//! Shared operations around resources
18
19// "Needless" lifetimes are useful for clarity
20#![allow(clippy::needless_lifetimes)]
21
22use alloc::sync::Arc;
23
24#[cfg(feature = "std")]
25extern crate std;
26use core::marker::{PhantomData, Send};
27use core::ops::Deref;
28#[cfg(feature = "std")]
29use std::sync::{RwLock, RwLockReadGuard};
30
31#[cfg(not(feature = "std"))]
32use 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
60pub enum ResourceEntry<T> {
61 Empty,
62 Owned(T),
63 Borrowed(Arc<RwLock<bool>>, *const T),
64}
65unsafe impl<T: Send> Send for ResourceEntry<T> {}
66
67pub struct LentResourceGuard<'a> {
68 flag: Arc<RwLock<bool>>,
69 already_revoked: bool,
70 _phantom: core::marker::PhantomData<&'a mut ()>,
71}
72impl<'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}
87impl<'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}
107pub struct BorrowedResourceGuard<'a, T> {
108 _flag: Option<RwLockReadGuard<'a, bool>>,
109 reference: &'a T,
110}
111impl<'a, T> Deref for BorrowedResourceGuard<'a, T> {
112 type Target = T;
113 fn deref(&self) -> &T {
114 self.reference
115 }
116}
117impl<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}