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}