Skip to main content

hyperlight_common/
resource.rs

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