Skip to content
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ default-members = [
members = [
"src/hyperlight_guest_capi",
"fuzz",
"src/hyperlight_component_util",
"src/hyperlight_component_macro",
]
# Guests have custom linker flags, so we need to exclude them from the workspace
exclude = [
Expand All @@ -30,6 +32,8 @@ hyperlight-common = { path = "src/hyperlight_common", version = "0.2.0", default
hyperlight-host = { path = "src/hyperlight_host", version = "0.2.0", default-features = false }
hyperlight-guest = { path = "src/hyperlight_guest", version = "0.2.0", default-features = false }
hyperlight-testing = { path = "src/hyperlight_testing", default-features = false }
hyperlight-component-util = { path = "src/hyperlight_component_util" }
hyperlight-component-macro = { path = "src/hyperlight_component_macro" }

[workspace.lints.rust]
unsafe_op_in_unsafe_fn = "deny"
Expand Down
2 changes: 2 additions & 0 deletions src/hyperlight_common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ log = "0.4.26"
tracing = { version = "0.1.41", optional = true }
strum = {version = "0.27", default-features = false, features = ["derive"]}
arbitrary = {version = "1.4.1", optional = true, features = ["derive"]}
spin = "0.9.8"

[features]
default = ["tracing"]
fuzzing = ["dep:arbitrary"]
std = []

[dev-dependencies]
hyperlight-testing = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions src/hyperlight_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ pub mod flatbuffer_wrappers;
mod flatbuffers;
/// cbindgen:ignore
pub mod mem;

pub mod resource;
154 changes: 154 additions & 0 deletions src/hyperlight_common/src/resource.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
//! Shared operations around resources

// "Needless" lifetimes are useful for clarity
#![allow(clippy::needless_lifetimes)]

use alloc::sync::Arc;

#[cfg(feature = "std")]
extern crate std;
use core::marker::{PhantomData, Send};
use core::ops::Deref;
#[cfg(feature = "std")]
use std::sync::{RwLock, RwLockReadGuard};

#[cfg(not(feature = "std"))]
use spin::{RwLock, RwLockReadGuard};

/// The semantics of component model resources are, pleasingly,
/// roughly compatible with those of Rust. Less pleasingly, it's not
/// terribly easy to show that statically.
///
/// In particular, if the host calls into the guest and gives it a
/// borrow of a resource, reentrant host function calls that use that
/// borrow need to be able to resolve the original reference and use
/// it in an appropriately scoped manner, but it is not simple to do
/// this, because the core Hyperlight machinery doesn't offer an easy
/// way to augment the host's context for the span of time of a guest
/// function call. This may be worth revisiting at some time, but in
/// the meantime, it's easier to just do it dynamically.
///
/// # Safety
/// Informally: this only creates SharedRead references, so having a
/// bunch of them going at once is fine. Safe Rust in the host can't
/// use any earlier borrows (potentially invalidating these) until
/// borrow passed into [`ResourceEntry::lend`] has expired. Because
/// that borrow outlives the [`LentResourceGuard`], it will not expire
/// until that destructor is called. That destructor ensures that (a)
/// there are no outstanding [`BorrowedResourceGuard`]s alive (since
/// they would be holding the read side of the [`RwLock`] if they
/// were), and that (b) the shared flag has been set to false, so
/// [`ResourceEntry::borrow`] will never create another borrow
pub enum ResourceEntry<T> {
Empty,
Owned(T),
Borrowed(Arc<RwLock<bool>>, *const T),
}
unsafe impl<T: Send> Send for ResourceEntry<T> {}

pub struct LentResourceGuard<'a> {
flag: Arc<RwLock<bool>>,
already_revoked: bool,
_phantom: core::marker::PhantomData<&'a mut ()>,
}
impl<'a> LentResourceGuard<'a> {
pub fn revoke_nonblocking(&mut self) -> bool {
#[cfg(feature = "std")]
let Ok(mut flag) = self.flag.try_write() else {
return false;
};
#[cfg(not(feature = "std"))]
let Some(mut flag) = self.flag.try_write() else {
return false;
};
*flag = false;
self.already_revoked = true;
true
}
}
impl<'a> Drop for LentResourceGuard<'a> {
fn drop(&mut self) {
if !self.already_revoked {
#[allow(unused_mut)] // it isn't actually unused
let mut guard = self.flag.write();
#[cfg(feature = "std")]
// If a mutex that is just protecting us from our own
// mistakes is poisoned, something is so seriously
// wrong that dying is a sensible response.
#[allow(clippy::unwrap_used)]
{
*guard.unwrap() = false;
}
#[cfg(not(feature = "std"))]
{
*guard = false;
}
}
}
}
pub struct BorrowedResourceGuard<'a, T> {
_flag: Option<RwLockReadGuard<'a, bool>>,
reference: &'a T,
}
impl<'a, T> Deref for BorrowedResourceGuard<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.reference
}
}
impl<T> ResourceEntry<T> {
pub fn give(x: T) -> ResourceEntry<T> {
ResourceEntry::Owned(x)
}
pub fn lend<'a>(x: &'a T) -> (LentResourceGuard<'a>, ResourceEntry<T>) {
let flag = Arc::new(RwLock::new(true));
(
LentResourceGuard {
flag: flag.clone(),
already_revoked: false,
_phantom: PhantomData {},
},
ResourceEntry::Borrowed(flag, x as *const T),
)
}
pub fn borrow<'a>(&'a self) -> Option<BorrowedResourceGuard<'a, T>> {
match self {
ResourceEntry::Empty => None,
ResourceEntry::Owned(t) => Some(BorrowedResourceGuard {
_flag: None,
reference: t,
}),
ResourceEntry::Borrowed(flag, t) => {
let guard = flag.read();
// If a mutex that is just protecting us from our own
// mistakes is poisoned, something is so seriously
// wrong that dying is a sensible response.
#[allow(clippy::unwrap_used)]
let flag = {
#[cfg(feature = "std")]
{
guard.unwrap()
}
#[cfg(not(feature = "std"))]
{
guard
}
};
if *flag {
Some(BorrowedResourceGuard {
_flag: Some(flag),
reference: unsafe { &**t },
})
} else {
None
}
}
}
}
pub fn take(&mut self) -> Option<T> {
match core::mem::replace(self, ResourceEntry::Empty) {
ResourceEntry::Owned(t) => Some(t),
_ => None,
}
}
}
25 changes: 25 additions & 0 deletions src/hyperlight_component_macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "hyperlight-component-macro"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
readme.workspace = true
description = """
Procedural macros to generate Hyperlight host and guest bindings from component types
"""

[lib]
name = "hyperlight_component_macro"
proc-macro = true

[dependencies]
wasmparser = { version = "0.224.0" }
quote = { version = "1.0.38" }
proc-macro2 = { version = "1.0.93" }
syn = { version = "2.0.96" }
itertools = { version = "0.14.0" }
prettyplease = { version = "0.2.31" }
hyperlight-component-util = { workspace = true }
18 changes: 18 additions & 0 deletions src/hyperlight_component_macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
extern crate proc_macro;

use hyperlight_component_util::*;

#[proc_macro]
pub fn host_bindgen(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let path: Option<syn::LitStr> = syn::parse_macro_input!(input as Option<syn::LitStr>);
let path = path
.map(|x| x.value().into())
.unwrap_or_else(|| std::env::var_os("HYPERLIGHT_WASM_WORLD").unwrap());
util::read_wit_type_from_file(path, |kebab_name, ct| {
let decls = emit::run_state(false, |s| {
rtypes::emit_toplevel(s, &kebab_name, ct);
host::emit_toplevel(s, &kebab_name, ct);
});
util::emit_decls(decls).into()
})
}
23 changes: 23 additions & 0 deletions src/hyperlight_component_util/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "hyperlight-component-util"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
readme.workspace = true
description = """
Shared implementation for the procedural macros that generate Hyperlight host and guest bindings from component types
"""

[lib]
name = "hyperlight_component_util"

[dependencies]
wasmparser = { version = "0.224.0" }
quote = { version = "1.0.38" }
proc-macro2 = { version = "1.0.93" }
syn = { version = "2.0.96" }
itertools = { version = "0.14.0" }
prettyplease = { version = "0.2.31" }
Loading