Skip to content

Commit 08416e2

Browse files
cpetigalexcrichton
andauthored
Rust: Provide a trait method to (optionally) control resource allocation (#1625)
* Resource allocation override option * custom resource allocation * cut the dirty tricks out of the arena * hide that it is an Option (typedef), put underscore to the end * Some review comments of mine and refactorings * Fix doc example --------- Co-authored-by: Alex Crichton <alex@alexcrichton.com>
1 parent 7a6dd1a commit 08416e2

9 files changed

Lines changed: 267 additions & 24 deletions

File tree

crates/guest-rust/src/examples/_4_exported_resources.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ crate::generate!({
2323
}
2424
}
2525
"#,
26+
runtime_path: "crate::rt", // only needed for this in-crate example.
2627
});

crates/guest-rust/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,8 @@ pub mod examples;
887887
#[doc(hidden)]
888888
pub mod rt;
889889

890+
pub mod resource;
891+
890892
#[cfg(feature = "inter-task-wakeup")]
891893
pub use rt::async_support::UnitStreamOps;
892894
#[cfg(feature = "async-spawn")]

crates/guest-rust/src/resource.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! Helper traits, types, and utilities for managing resources in the component
2+
//! model.
3+
4+
/// A trait implemented by all resources that a component might export.
5+
///
6+
/// This is an implementation detail primarily for the code generated by
7+
/// exported resources. The primary purpose of this trait is to serve as an
8+
/// abstraction for the in-memory storage of a resource.
9+
pub trait Resource: Sized + 'static {
10+
/// The type which is actually stored in-memory for this resource.
11+
///
12+
/// By default this is `Option<T>`.
13+
type Rep: ResourceRep<Self>;
14+
}
15+
16+
impl<T: 'static> Resource for T {
17+
type Rep = Option<T>;
18+
}
19+
20+
/// A trait used to define how to access the underlying data `T` from an
21+
/// in-memory representation.
22+
///
23+
/// This is used as a bound on the [`Resource::Rep`] associated type which is in
24+
/// turn used to access data within a resources.
25+
pub unsafe trait ResourceRep<T> {
26+
/// Creates a new instance of `Self` which wraps the provided data.
27+
fn rep_new(inner: T) -> Self;
28+
29+
/// Acquires `&T` from a raw pointer to `Self`.
30+
unsafe fn rep_as_ref<'a>(ptr: *const Self) -> &'a T;
31+
32+
/// Acquires `&mut T` from a raw pointer to `Self`.
33+
unsafe fn rep_as_mut<'a>(ptr: *mut Self) -> &'a mut T;
34+
35+
/// Takes the value out of `Self` at the provided pointer.
36+
///
37+
/// Note that `ptr` will later be deallocated meaning that it must not run
38+
/// the destructor of `T` after this method is called. This is guaranteed to
39+
/// be called at most once, however. Additionally after calling this method
40+
/// it's guaranteed that the `rep_as_*` method above will not be called.
41+
unsafe fn rep_take<'a>(ptr: *mut Self) -> T;
42+
}
43+
44+
unsafe impl<T> ResourceRep<T> for Option<T> {
45+
fn rep_new(inner: T) -> Option<T> {
46+
Some(inner)
47+
}
48+
unsafe fn rep_as_ref<'a>(ptr: *const Option<T>) -> &'a T {
49+
unsafe { (*ptr).as_ref().unwrap() }
50+
}
51+
unsafe fn rep_as_mut<'a>(ptr: *mut Option<T>) -> &'a mut T {
52+
unsafe { (*ptr).as_mut().unwrap() }
53+
}
54+
unsafe fn rep_take(ptr: *mut Option<T>) -> T {
55+
unsafe { (*ptr).take().unwrap() }
56+
}
57+
}

crates/guest-rust/src/rt/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use core::alloc::Layout;
22
use core::ptr::{self, NonNull};
33

4+
pub use crate::resource::{Resource, ResourceRep};
5+
46
// Re-export `bitflags` so that we can reference it from macros.
57
#[cfg(feature = "bitflags")]
68
pub use bitflags;

crates/rust/src/bindgen.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,9 @@ impl Bindgen for FunctionBindgen<'_, '_> {
406406
let result = if is_own {
407407
format!("{name}::from_handle({op} as u32)")
408408
} else if self.r#gen.is_exported_resource(*resource) {
409-
format!("{name}Borrow::lift({op} as u32 as usize)")
409+
format!(
410+
"{name}Borrow::lift(core::ptr::with_exposed_provenance({op} as u32 as usize))"
411+
)
410412
} else {
411413
let tmp = format!("handle{}", self.tmp());
412414
self.handle_decls.push(format!("let {tmp};"));

crates/rust/src/interface.rs

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -196,9 +196,10 @@ impl<'i> InterfaceGenerator<'i> {
196196
.map(|(resource, (trait_name, ..))| (resource.unwrap(), trait_name.as_str())),
197197
)
198198
}
199+
let rt = self.r#gen.runtime_path().to_string();
199200

200201
for (resource, (trait_name, methods)) in traits.iter() {
201-
uwriteln!(self.src, "pub trait {trait_name}: 'static {{");
202+
uwriteln!(self.src, "pub trait {trait_name}: {rt}::Resource {{");
202203
let resource = resource.unwrap();
203204
let resource_name = self.resolve.types[resource].name.as_ref().unwrap();
204205
let (_, interface_name) = interface.unwrap();
@@ -239,6 +240,55 @@ fn _resource_rep(handle: u32) -> *mut u8
239240
240241
"#
241242
);
243+
let box_path = self.path_to_box();
244+
uwriteln!(
245+
self.src,
246+
r#"
247+
/// Place this resource's representation into a location with a stable pointer,
248+
/// returning a pointer to that location.
249+
///
250+
/// This method is used to place `val` on the heap, for example, or possibly in
251+
/// an arena. The returned pointer must remain valid for the lifetime of this
252+
/// resource. The default implementation of this metho will place `val` onto the
253+
/// heap with `Box`. The returned pointer will be deallocated by the sibling
254+
/// `resource_from_raw_` function.
255+
///
256+
/// # Safety
257+
///
258+
/// Note that this method is not `unsafe` to call, but it is `unsafe` to
259+
/// define. When overriding this method you must additionally override the
260+
/// `resource_from_raw_` to insert an appropriate deallocation for the returned
261+
/// pointer. In the future this might become a default associated trait bound,
262+
/// but that's not stable in Rust right now.
263+
#[doc(hidden)]
264+
unsafe fn resource_into_raw_(val: Self::Rep) -> *mut Self::Rep
265+
{{
266+
{box_path}::into_raw({box_path}::new(val))
267+
}}
268+
269+
/// Consumes and deallocates a pointer previously returned by
270+
/// `resource_into_raw_`.
271+
///
272+
/// This function is used to deallocate resources and allocations associated
273+
/// with the allocation routine when creating a resource. The default
274+
/// implementation of this method will read `handle`'s value and deallocate it
275+
/// as a `Box`.
276+
///
277+
/// Note that when overriding this method you'll almost surely want to override
278+
/// `resource_into_raw_` as well.
279+
///
280+
/// # Safety
281+
///
282+
/// This method is only safe to call with pointers previously created by
283+
/// `resource_into_raw_`. For more information see `Box::from_raw` for examples.
284+
#[doc(hidden)]
285+
unsafe fn resource_from_raw_(handle: *mut Self::Rep) -> Self::Rep
286+
{{
287+
*unsafe {{ {box_path}::from_raw(handle) }}
288+
}}
289+
290+
"#
291+
);
242292
for method in methods {
243293
self.src.push_str(method);
244294
}
@@ -2716,7 +2766,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> {
27162766
Identifier::World(_) => unimplemented!("resource exports from worlds"),
27172767
Identifier::StreamOrFuturePayload => unreachable!(),
27182768
};
2719-
let box_path = self.path_to_box();
2769+
let rt = self.r#gen.runtime_path();
27202770
uwriteln!(
27212771
self.src,
27222772
r#"
@@ -2726,8 +2776,6 @@ pub struct {camel} {{
27262776
handle: {resource}<{camel}>,
27272777
}}
27282778
2729-
type _{camel}Rep<T> = Option<T>;
2730-
27312779
impl {camel} {{
27322780
/// Creates a new resource from the specified representation.
27332781
///
@@ -2736,31 +2784,30 @@ impl {camel} {{
27362784
/// create a handle. The owned handle is then returned as `{camel}`.
27372785
pub fn new<T: Guest{camel}>(val: T) -> Self {{
27382786
Self::type_guard::<T>();
2739-
let val: _{camel}Rep<T> = Some(val);
2740-
let ptr: *mut _{camel}Rep<T> =
2741-
{box_path}::into_raw({box_path}::new(val));
2787+
let rep = <T::Rep as {rt}::ResourceRep<T>>::rep_new(val);
27422788
unsafe {{
2789+
let ptr = T::resource_into_raw_(rep);
27432790
Self::from_handle(T::_resource_new(ptr.cast()))
27442791
}}
27452792
}}
27462793
27472794
/// Gets access to the underlying `T` which represents this resource.
27482795
pub fn get<T: Guest{camel}>(&self) -> &T {{
2749-
let ptr = unsafe {{ &*self.as_ptr::<T>() }};
2750-
ptr.as_ref().unwrap()
2796+
let ptr = self.as_ptr::<T>();
2797+
unsafe {{ <T::Rep as {rt}::ResourceRep<T>>::rep_as_ref(ptr) }}
27512798
}}
27522799
27532800
/// Gets mutable access to the underlying `T` which represents this
27542801
/// resource.
27552802
pub fn get_mut<T: Guest{camel}>(&mut self) -> &mut T {{
2756-
let ptr = unsafe {{ &mut *self.as_ptr::<T>() }};
2757-
ptr.as_mut().unwrap()
2803+
let ptr = self.as_ptr::<T>();
2804+
unsafe {{ <T::Rep as {rt}::ResourceRep<T>>::rep_as_mut(ptr) }}
27582805
}}
27592806
27602807
/// Consumes this resource and returns the underlying `T`.
27612808
pub fn into_inner<T: Guest{camel}>(self) -> T {{
2762-
let ptr = unsafe {{ &mut *self.as_ptr::<T>() }};
2763-
ptr.take().unwrap()
2809+
let ptr = self.as_ptr::<T>();
2810+
unsafe {{ <T::Rep as {rt}::ResourceRep<T>>::rep_take(ptr) }}
27642811
}}
27652812
27662813
#[doc(hidden)]
@@ -2783,7 +2830,7 @@ impl {camel} {{
27832830
// It's theoretically possible to implement the `Guest{camel}` trait twice
27842831
// so guard against using it with two different types here.
27852832
#[doc(hidden)]
2786-
fn type_guard<T: 'static>() {{
2833+
fn type_guard<T: {rt}::Resource>() {{
27872834
use core::any::TypeId;
27882835
static mut LAST_TYPE: Option<TypeId> = None;
27892836
unsafe {{
@@ -2797,12 +2844,14 @@ impl {camel} {{
27972844
}}
27982845
27992846
#[doc(hidden)]
2800-
pub unsafe fn dtor<T: 'static>(handle: *mut u8) {{
2847+
pub unsafe fn dtor<T: Guest{camel}>(handle: *mut u8) {{
28012848
Self::type_guard::<T>();
2802-
let _ = unsafe {{ {box_path}::from_raw(handle as *mut _{camel}Rep<T>) }};
2849+
unsafe {{
2850+
let _rep = T::resource_from_raw_(handle.cast());
2851+
}}
28032852
}}
28042853
2805-
fn as_ptr<T: Guest{camel}>(&self) -> *mut _{camel}Rep<T> {{
2854+
fn as_ptr<T: Guest{camel}>(&self) -> *mut T::Rep {{
28062855
{camel}::type_guard::<T>();
28072856
T::_resource_rep(self.handle()).cast()
28082857
}}
@@ -2813,29 +2862,29 @@ impl {camel} {{
28132862
#[derive(Debug)]
28142863
#[repr(transparent)]
28152864
pub struct {camel}Borrow<'a> {{
2816-
rep: *mut u8,
2865+
rep: *const u8,
28172866
_marker: core::marker::PhantomData<&'a {camel}>,
28182867
}}
28192868
28202869
impl<'a> {camel}Borrow<'a>{{
28212870
#[doc(hidden)]
2822-
pub unsafe fn lift(rep: usize) -> Self {{
2871+
pub unsafe fn lift(rep: *const u8) -> Self {{
28232872
Self {{
2824-
rep: rep as *mut u8,
2873+
rep,
28252874
_marker: core::marker::PhantomData,
28262875
}}
28272876
}}
28282877
28292878
/// Gets access to the underlying `T` in this resource.
28302879
pub fn get<T: Guest{camel}>(&self) -> &'a T {{
2831-
let ptr = unsafe {{ &mut *self.as_ptr::<T>() }};
2832-
ptr.as_ref().unwrap()
2880+
let ptr = self.as_ptr::<T>();
2881+
unsafe {{ <T::Rep as {rt}::ResourceRep<T>>::rep_as_ref(ptr) }}
28332882
}}
28342883
28352884
// NB: mutable access is not allowed due to the component model allowing
28362885
// multiple borrows of the same resource.
28372886
2838-
fn as_ptr<T: 'static>(&self) -> *mut _{camel}Rep<T> {{
2887+
fn as_ptr<T: Guest{camel}>(&self) -> *const T::Rep {{
28392888
{camel}::type_guard::<T>();
28402889
self.rep.cast()
28412890
}}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
include!(env!("BINDINGS"));
2+
3+
use crate::test::arena_allocated_resources::to_test::Thing;
4+
5+
struct Component;
6+
7+
export!(Component);
8+
9+
impl Guest for Component {
10+
fn run() {
11+
let thing1 = Thing::new(3);
12+
let thing2 = Thing::new(5);
13+
assert_eq!(3, thing1.get());
14+
assert_eq!(5, thing2.get());
15+
}
16+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
include!(env!("BINDINGS"));
2+
3+
use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing};
4+
5+
export!(Component);
6+
7+
struct Component;
8+
9+
impl Guest for Component {
10+
type Thing = MyThing;
11+
}
12+
13+
mod arena {
14+
15+
use core::sync::atomic::{AtomicUsize, Ordering};
16+
use core::{cell::UnsafeCell, mem::MaybeUninit};
17+
18+
/// A simple no_std arena allocator for fixed-size allocations.
19+
///
20+
/// The arena allocates items of type T sequentially from a pre-allocated buffer
21+
/// and does not support individual deallocation. Memory is reclaimed
22+
/// only when the entire arena is reset.
23+
pub struct Arena<T, const SIZE: usize> {
24+
buffer: [UnsafeCell<MaybeUninit<T>>; SIZE],
25+
offset: AtomicUsize,
26+
}
27+
28+
// Element allocation is atomic and elements are exclusively handed out after allocation,
29+
// so the arena can be send to other threads and simultaneosly accessed by multiple threads
30+
unsafe impl<T: Sync, const SIZE: usize> Sync for Arena<T, SIZE> {}
31+
unsafe impl<T: Send, const SIZE: usize> Send for Arena<T, SIZE> {}
32+
33+
impl<T: Default, const SIZE: usize> Arena<T, SIZE> {
34+
pub const fn new() -> Self {
35+
Self {
36+
buffer: [const { UnsafeCell::new(MaybeUninit::uninit()) }; SIZE],
37+
offset: AtomicUsize::new(0),
38+
}
39+
}
40+
41+
/// Allocates space for a single item of type T.
42+
/// Returns a mutable reference to the allocated memory, or None if there's insufficient space.
43+
pub fn alloc_one(&self) -> Option<&mut T> {
44+
// short circuit the exhausted state (don't increment if full)
45+
if self.offset.load(Ordering::Relaxed) >= SIZE {
46+
None
47+
} else {
48+
// now try to allocate for real
49+
let pos = self.offset.fetch_add(1, Ordering::Acquire);
50+
if pos >= SIZE {
51+
// now self.offset is already beyond SIZE, reduce our increment and return none
52+
self.offset.fetch_sub(1, Ordering::Release);
53+
None
54+
} else {
55+
let ptr = self.buffer[pos].get();
56+
// SAFETY: we demand exclusive ownership of the item in the arena
57+
let uninit = unsafe { &mut *ptr };
58+
Some(uninit.write(Default::default()))
59+
}
60+
}
61+
}
62+
}
63+
}
64+
65+
use arena::Arena;
66+
67+
#[derive(Clone)]
68+
struct MyThing {
69+
contents: u32,
70+
}
71+
72+
static ARENA: Arena<Option<MyThing>, 4> = Arena::new();
73+
74+
impl GuestThing for MyThing {
75+
fn new(v: u32) -> MyThing {
76+
MyThing { contents: v }
77+
}
78+
79+
fn get(&self) -> u32 {
80+
self.contents
81+
}
82+
83+
unsafe fn resource_into_raw_(val: Self::Rep) -> *mut Self::Rep {
84+
val.and_then(|v| {
85+
ARENA.alloc_one().map(|x| {
86+
*x = Some(v);
87+
x as *mut _
88+
})
89+
})
90+
.unwrap_or(core::ptr::null_mut())
91+
}
92+
93+
unsafe fn resource_from_raw_(handle: *mut Self::Rep) -> Self::Rep {
94+
unsafe { &mut *handle }.take()
95+
}
96+
}

0 commit comments

Comments
 (0)