diff --git a/crates/guest-rust/src/examples/_4_exported_resources.rs b/crates/guest-rust/src/examples/_4_exported_resources.rs index 5d1281be5..e47914d4c 100644 --- a/crates/guest-rust/src/examples/_4_exported_resources.rs +++ b/crates/guest-rust/src/examples/_4_exported_resources.rs @@ -23,4 +23,5 @@ crate::generate!({ } } "#, + runtime_path: "crate::rt", // only needed for this in-crate example. }); diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index 3e55de02a..3b3d7f4f5 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -878,6 +878,8 @@ pub mod examples; #[doc(hidden)] pub mod rt; +pub mod resource; + #[cfg(feature = "inter-task-wakeup")] pub use rt::async_support::UnitStreamOps; #[cfg(feature = "async-spawn")] diff --git a/crates/guest-rust/src/resource.rs b/crates/guest-rust/src/resource.rs new file mode 100644 index 000000000..075502fad --- /dev/null +++ b/crates/guest-rust/src/resource.rs @@ -0,0 +1,57 @@ +//! Helper traits, types, and utilities for managing resources in the component +//! model. + +/// A trait implemented by all resources that a component might export. +/// +/// This is an implementation detail primarily for the code generated by +/// exported resources. The primary purpose of this trait is to serve as an +/// abstraction for the in-memory storage of a resource. +pub trait Resource: Sized + 'static { + /// The type which is actually stored in-memory for this resource. + /// + /// By default this is `Option`. + type Rep: ResourceRep; +} + +impl Resource for T { + type Rep = Option; +} + +/// A trait used to define how to access the underlying data `T` from an +/// in-memory representation. +/// +/// This is used as a bound on the [`Resource::Rep`] associated type which is in +/// turn used to access data within a resources. +pub unsafe trait ResourceRep { + /// Creates a new instance of `Self` which wraps the provided data. + fn rep_new(inner: T) -> Self; + + /// Acquires `&T` from a raw pointer to `Self`. + unsafe fn rep_as_ref<'a>(ptr: *const Self) -> &'a T; + + /// Acquires `&mut T` from a raw pointer to `Self`. + unsafe fn rep_as_mut<'a>(ptr: *mut Self) -> &'a mut T; + + /// Takes the value out of `Self` at the provided pointer. + /// + /// Note that `ptr` will later be deallocated meaning that it must not run + /// the destructor of `T` after this method is called. This is guaranteed to + /// be called at most once, however. Additionally after calling this method + /// it's guaranteed that the `rep_as_*` method above will not be called. + unsafe fn rep_take<'a>(ptr: *mut Self) -> T; +} + +unsafe impl ResourceRep for Option { + fn rep_new(inner: T) -> Option { + Some(inner) + } + unsafe fn rep_as_ref<'a>(ptr: *const Option) -> &'a T { + unsafe { (*ptr).as_ref().unwrap() } + } + unsafe fn rep_as_mut<'a>(ptr: *mut Option) -> &'a mut T { + unsafe { (*ptr).as_mut().unwrap() } + } + unsafe fn rep_take(ptr: *mut Option) -> T { + unsafe { (*ptr).take().unwrap() } + } +} diff --git a/crates/guest-rust/src/rt/mod.rs b/crates/guest-rust/src/rt/mod.rs index 40736f6ef..e62c52640 100644 --- a/crates/guest-rust/src/rt/mod.rs +++ b/crates/guest-rust/src/rt/mod.rs @@ -1,6 +1,8 @@ use core::alloc::Layout; use core::ptr::{self, NonNull}; +pub use crate::resource::{Resource, ResourceRep}; + // Re-export `bitflags` so that we can reference it from macros. #[cfg(feature = "bitflags")] pub use bitflags; diff --git a/crates/rust/src/bindgen.rs b/crates/rust/src/bindgen.rs index bee880167..767beb6a9 100644 --- a/crates/rust/src/bindgen.rs +++ b/crates/rust/src/bindgen.rs @@ -406,7 +406,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = if is_own { format!("{name}::from_handle({op} as u32)") } else if self.r#gen.is_exported_resource(*resource) { - format!("{name}Borrow::lift({op} as u32 as usize)") + format!( + "{name}Borrow::lift(core::ptr::with_exposed_provenance({op} as u32 as usize))" + ) } else { let tmp = format!("handle{}", self.tmp()); self.handle_decls.push(format!("let {tmp};")); diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index c81c3271e..148249be0 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -196,9 +196,10 @@ impl<'i> InterfaceGenerator<'i> { .map(|(resource, (trait_name, ..))| (resource.unwrap(), trait_name.as_str())), ) } + let rt = self.r#gen.runtime_path().to_string(); for (resource, (trait_name, methods)) in traits.iter() { - uwriteln!(self.src, "pub trait {trait_name}: 'static {{"); + uwriteln!(self.src, "pub trait {trait_name}: {rt}::Resource {{"); let resource = resource.unwrap(); let resource_name = self.resolve.types[resource].name.as_ref().unwrap(); let (_, interface_name) = interface.unwrap(); @@ -239,6 +240,55 @@ fn _resource_rep(handle: u32) -> *mut u8 "# ); + let box_path = self.path_to_box(); + uwriteln!( + self.src, + r#" +/// Place this resource's representation into a location with a stable pointer, +/// returning a pointer to that location. +/// +/// This method is used to place `val` on the heap, for example, or possibly in +/// an arena. The returned pointer must remain valid for the lifetime of this +/// resource. The default implementation of this metho will place `val` onto the +/// heap with `Box`. The returned pointer will be deallocated by the sibling +/// `resource_from_raw_` function. +/// +/// # Safety +/// +/// Note that this method is not `unsafe` to call, but it is `unsafe` to +/// define. When overriding this method you must additionally override the +/// `resource_from_raw_` to insert an appropriate deallocation for the returned +/// pointer. In the future this might become a default associated trait bound, +/// but that's not stable in Rust right now. +#[doc(hidden)] +unsafe fn resource_into_raw_(val: Self::Rep) -> *mut Self::Rep +{{ + {box_path}::into_raw({box_path}::new(val)) +}} + +/// Consumes and deallocates a pointer previously returned by +/// `resource_into_raw_`. +/// +/// This function is used to deallocate resources and allocations associated +/// with the allocation routine when creating a resource. The default +/// implementation of this method will read `handle`'s value and deallocate it +/// as a `Box`. +/// +/// Note that when overriding this method you'll almost surely want to override +/// `resource_into_raw_` as well. +/// +/// # Safety +/// +/// This method is only safe to call with pointers previously created by +/// `resource_into_raw_`. For more information see `Box::from_raw` for examples. +#[doc(hidden)] +unsafe fn resource_from_raw_(handle: *mut Self::Rep) -> Self::Rep +{{ + *unsafe {{ {box_path}::from_raw(handle) }} +}} + + "# + ); for method in methods { self.src.push_str(method); } @@ -2716,7 +2766,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { Identifier::World(_) => unimplemented!("resource exports from worlds"), Identifier::StreamOrFuturePayload => unreachable!(), }; - let box_path = self.path_to_box(); + let rt = self.r#gen.runtime_path(); uwriteln!( self.src, r#" @@ -2726,8 +2776,6 @@ pub struct {camel} {{ handle: {resource}<{camel}>, }} -type _{camel}Rep = Option; - impl {camel} {{ /// Creates a new resource from the specified representation. /// @@ -2736,31 +2784,30 @@ impl {camel} {{ /// create a handle. The owned handle is then returned as `{camel}`. pub fn new(val: T) -> Self {{ Self::type_guard::(); - let val: _{camel}Rep = Some(val); - let ptr: *mut _{camel}Rep = - {box_path}::into_raw({box_path}::new(val)); + let rep = >::rep_new(val); unsafe {{ + let ptr = T::resource_into_raw_(rep); Self::from_handle(T::_resource_new(ptr.cast())) }} }} /// Gets access to the underlying `T` which represents this resource. pub fn get(&self) -> &T {{ - let ptr = unsafe {{ &*self.as_ptr::() }}; - ptr.as_ref().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_as_ref(ptr) }} }} /// Gets mutable access to the underlying `T` which represents this /// resource. pub fn get_mut(&mut self) -> &mut T {{ - let ptr = unsafe {{ &mut *self.as_ptr::() }}; - ptr.as_mut().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_as_mut(ptr) }} }} /// Consumes this resource and returns the underlying `T`. pub fn into_inner(self) -> T {{ - let ptr = unsafe {{ &mut *self.as_ptr::() }}; - ptr.take().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_take(ptr) }} }} #[doc(hidden)] @@ -2783,7 +2830,7 @@ impl {camel} {{ // It's theoretically possible to implement the `Guest{camel}` trait twice // so guard against using it with two different types here. #[doc(hidden)] - fn type_guard() {{ + fn type_guard() {{ use core::any::TypeId; static mut LAST_TYPE: Option = None; unsafe {{ @@ -2797,12 +2844,14 @@ impl {camel} {{ }} #[doc(hidden)] - pub unsafe fn dtor(handle: *mut u8) {{ + pub unsafe fn dtor(handle: *mut u8) {{ Self::type_guard::(); - let _ = unsafe {{ {box_path}::from_raw(handle as *mut _{camel}Rep) }}; + unsafe {{ + let _rep = T::resource_from_raw_(handle.cast()); + }} }} - fn as_ptr(&self) -> *mut _{camel}Rep {{ + fn as_ptr(&self) -> *mut T::Rep {{ {camel}::type_guard::(); T::_resource_rep(self.handle()).cast() }} @@ -2813,29 +2862,29 @@ impl {camel} {{ #[derive(Debug)] #[repr(transparent)] pub struct {camel}Borrow<'a> {{ - rep: *mut u8, + rep: *const u8, _marker: core::marker::PhantomData<&'a {camel}>, }} impl<'a> {camel}Borrow<'a>{{ #[doc(hidden)] - pub unsafe fn lift(rep: usize) -> Self {{ + pub unsafe fn lift(rep: *const u8) -> Self {{ Self {{ - rep: rep as *mut u8, + rep, _marker: core::marker::PhantomData, }} }} /// Gets access to the underlying `T` in this resource. pub fn get(&self) -> &'a T {{ - let ptr = unsafe {{ &mut *self.as_ptr::() }}; - ptr.as_ref().unwrap() + let ptr = self.as_ptr::(); + unsafe {{ >::rep_as_ref(ptr) }} }} // NB: mutable access is not allowed due to the component model allowing // multiple borrows of the same resource. - fn as_ptr(&self) -> *mut _{camel}Rep {{ + fn as_ptr(&self) -> *const T::Rep {{ {camel}::type_guard::(); self.rep.cast() }} diff --git a/tests/runtime/rust/arena-allocated-resources/runner.rs b/tests/runtime/rust/arena-allocated-resources/runner.rs new file mode 100644 index 000000000..665a07632 --- /dev/null +++ b/tests/runtime/rust/arena-allocated-resources/runner.rs @@ -0,0 +1,16 @@ +include!(env!("BINDINGS")); + +use crate::test::arena_allocated_resources::to_test::Thing; + +struct Component; + +export!(Component); + +impl Guest for Component { + fn run() { + let thing1 = Thing::new(3); + let thing2 = Thing::new(5); + assert_eq!(3, thing1.get()); + assert_eq!(5, thing2.get()); + } +} diff --git a/tests/runtime/rust/arena-allocated-resources/test.rs b/tests/runtime/rust/arena-allocated-resources/test.rs new file mode 100644 index 000000000..71fb684e0 --- /dev/null +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -0,0 +1,96 @@ +include!(env!("BINDINGS")); + +use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing}; + +export!(Component); + +struct Component; + +impl Guest for Component { + type Thing = MyThing; +} + +mod arena { + + use core::sync::atomic::{AtomicUsize, Ordering}; + use core::{cell::UnsafeCell, mem::MaybeUninit}; + + /// A simple no_std arena allocator for fixed-size allocations. + /// + /// The arena allocates items of type T sequentially from a pre-allocated buffer + /// and does not support individual deallocation. Memory is reclaimed + /// only when the entire arena is reset. + pub struct Arena { + buffer: [UnsafeCell>; SIZE], + offset: AtomicUsize, + } + + // Element allocation is atomic and elements are exclusively handed out after allocation, + // so the arena can be send to other threads and simultaneosly accessed by multiple threads + unsafe impl Sync for Arena {} + unsafe impl Send for Arena {} + + impl Arena { + pub const fn new() -> Self { + Self { + buffer: [const { UnsafeCell::new(MaybeUninit::uninit()) }; SIZE], + offset: AtomicUsize::new(0), + } + } + + /// Allocates space for a single item of type T. + /// Returns a mutable reference to the allocated memory, or None if there's insufficient space. + pub fn alloc_one(&self) -> Option<&mut T> { + // short circuit the exhausted state (don't increment if full) + if self.offset.load(Ordering::Relaxed) >= SIZE { + None + } else { + // now try to allocate for real + let pos = self.offset.fetch_add(1, Ordering::Acquire); + if pos >= SIZE { + // now self.offset is already beyond SIZE, reduce our increment and return none + self.offset.fetch_sub(1, Ordering::Release); + None + } else { + let ptr = self.buffer[pos].get(); + // SAFETY: we demand exclusive ownership of the item in the arena + let uninit = unsafe { &mut *ptr }; + Some(uninit.write(Default::default())) + } + } + } + } +} + +use arena::Arena; + +#[derive(Clone)] +struct MyThing { + contents: u32, +} + +static ARENA: Arena, 4> = Arena::new(); + +impl GuestThing for MyThing { + fn new(v: u32) -> MyThing { + MyThing { contents: v } + } + + fn get(&self) -> u32 { + self.contents + } + + unsafe fn resource_into_raw_(val: Self::Rep) -> *mut Self::Rep { + val.and_then(|v| { + ARENA.alloc_one().map(|x| { + *x = Some(v); + x as *mut _ + }) + }) + .unwrap_or(core::ptr::null_mut()) + } + + unsafe fn resource_from_raw_(handle: *mut Self::Rep) -> Self::Rep { + unsafe { &mut *handle }.take() + } +} diff --git a/tests/runtime/rust/arena-allocated-resources/test.wit b/tests/runtime/rust/arena-allocated-resources/test.wit new file mode 100644 index 000000000..9a2ffc934 --- /dev/null +++ b/tests/runtime/rust/arena-allocated-resources/test.wit @@ -0,0 +1,18 @@ +package test:arena-allocated-resources; + +interface to-test { + resource thing { + constructor(v: u32); + get: func() -> u32; + } +} + +world test { + export to-test; +} + +world runner { + import to-test; + + export run: func(); +}