From 16a9bd95717f2548796a82ad8b5aa9615608207f Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Wed, 27 May 2026 00:28:27 +0200 Subject: [PATCH 1/4] Resource allocation override option --- crates/rust/src/interface.rs | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index c81c3271e..193789331 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -337,6 +337,30 @@ macro_rules! {macro_name} {{ resource_traits: impl Iterator, ) { uwriteln!(self.src, "pub trait {trait_name} {{"); + let box_path = self.path_to_box(); + uwriteln!( + self.src, + r#"#[doc(hidden)] +/// Place the value on the heap or in an arena, return the raw pointer. +/// Override for custom resource allocators. +fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized +{{ + {box_path}::into_raw({box_path}::new(val)) +}} + +#[doc(hidden)] +/// Consumes the value from the handle, handle is invalid afterwards. +/// +/// # Safety +/// +/// See Box::from_raw +unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized +{{ + *unsafe {{ {box_path}::from_raw(handle) }} +}} + + "# + ); for (id, trait_name) in resource_traits { let name = self.resolve.types[id] .name @@ -2716,7 +2740,6 @@ 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(); uwriteln!( self.src, r#" @@ -2737,8 +2760,7 @@ impl {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 ptr: *mut _{camel}Rep = T::_resource_into_raw(val); unsafe {{ Self::from_handle(T::_resource_new(ptr.cast())) }} @@ -2797,9 +2819,9 @@ 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) }}; + let _ = unsafe {{ T::_resource_from_raw(handle as *mut _{camel}Rep) }}; }} fn as_ptr(&self) -> *mut _{camel}Rep {{ From e9cc4e3eb75a79c26d8fb676227707a9740d5119 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sat, 6 Jun 2026 22:48:45 +0200 Subject: [PATCH 2/4] custom resource allocation --- crates/rust/src/interface.rs | 48 +++---- .../rust/arena-allocated-resources/runner.rs | 16 +++ .../rust/arena-allocated-resources/test.rs | 127 ++++++++++++++++++ .../rust/arena-allocated-resources/test.wit | 18 +++ 4 files changed, 185 insertions(+), 24 deletions(-) create mode 100644 tests/runtime/rust/arena-allocated-resources/runner.rs create mode 100644 tests/runtime/rust/arena-allocated-resources/test.rs create mode 100644 tests/runtime/rust/arena-allocated-resources/test.wit diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 193789331..81fb71881 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -239,6 +239,30 @@ fn _resource_rep(handle: u32) -> *mut u8 "# ); + let box_path = self.path_to_box(); + uwriteln!( + self.src, + r#"#[doc(hidden)] +/// Place the value on the heap or in an arena, return the raw pointer. +/// Override for custom resource allocators. +fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized +{{ + {box_path}::into_raw({box_path}::new(val)) +}} + +#[doc(hidden)] +/// Consumes the value from the handle, handle is invalid afterwards. +/// +/// # Safety +/// +/// See Box::from_raw +unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized +{{ + *unsafe {{ {box_path}::from_raw(handle) }} +}} + + "# + ); for method in methods { self.src.push_str(method); } @@ -337,30 +361,6 @@ macro_rules! {macro_name} {{ resource_traits: impl Iterator, ) { uwriteln!(self.src, "pub trait {trait_name} {{"); - let box_path = self.path_to_box(); - uwriteln!( - self.src, - r#"#[doc(hidden)] -/// Place the value on the heap or in an arena, return the raw pointer. -/// Override for custom resource allocators. -fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized -{{ - {box_path}::into_raw({box_path}::new(val)) -}} - -#[doc(hidden)] -/// Consumes the value from the handle, handle is invalid afterwards. -/// -/// # Safety -/// -/// See Box::from_raw -unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized -{{ - *unsafe {{ {box_path}::from_raw(handle) }} -}} - - "# - ); for (id, trait_name) in resource_traits { let name = self.resolve.types[id] .name 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..cc746d697 --- /dev/null +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -0,0 +1,127 @@ +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::{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: [MaybeUninit; SIZE], + offset: usize, + } + + impl Arena { + /// 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(&mut self) -> Option<&mut T> { + if self.offset < SIZE { + let ptr = self.buffer[self.offset].as_mut_ptr(); + self.offset += 1; + Some(unsafe { &mut *ptr }) + } else { + None + } + } + } + + /// A static-safe wrapper for Arena that uses interior mutability. + /// + /// This allows an Arena to be stored in a static variable and accessed safely + /// in single-threaded contexts without requiring std or alloc. + /// + /// # Safety + /// + /// This type is safe to use in single-threaded environments. In multi-threaded + /// contexts, external synchronization is required. + pub struct StaticArena { + arena: UnsafeCell>, + } + + // SAFETY: StaticArena is Sync because we enforce single-threaded access through + // the API. It can be safely shared across threads as long as only one thread + // accesses it at a time (which is the responsibility of the user). + unsafe impl Sync for StaticArena where T: Sync {} + + // SAFETY: StaticArena is Send because the underlying Arena can be moved between + // threads, and T itself must be Send. + unsafe impl Send for StaticArena where T: Send {} + + impl StaticArena { + /// Creates a new static arena. + pub const fn new() -> Self { + StaticArena { + arena: UnsafeCell::new(Arena { + buffer: [const { MaybeUninit::uninit() }; SIZE], + offset: 0, + }), + } + } + + /// Gets mutable access to the arena. + /// + /// # Safety + /// + /// This is safe in single-threaded contexts. In multi-threaded contexts, + /// the caller must ensure exclusive access. + #[inline] + pub fn get_mut(&self) -> &mut Arena { + unsafe { &mut *self.arena.get() } + } + + /// Allocates a single item. + pub fn alloc_one(&self) -> Option<&mut T> { + self.get_mut().alloc_one() + } + } +} + +use arena::StaticArena; + +#[derive(Clone)] +struct MyThing { + contents: u32, +} + +static ARENA: StaticArena, 4> = StaticArena::new(); + +impl GuestThing for MyThing { + fn new(v: u32) -> MyThing { + MyThing { contents: v } + } + fn get(&self) -> u32 { + self.contents + } + fn _resource_into_raw(val: Option) -> *mut Option + where + Self: Sized, + { + 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 Option) -> Option + where + Self: Sized, + { + let res = unsafe { &mut *handle }.take(); + res + } +} 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(); +} From 4263a8be6b52855231de43e2d5f34cfbcf7eda95 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Sun, 14 Jun 2026 20:52:46 +0200 Subject: [PATCH 3/4] cut the dirty tricks out of the arena --- .../rust/arena-allocated-resources/test.rs | 94 +++++++------------ 1 file changed, 35 insertions(+), 59 deletions(-) diff --git a/tests/runtime/rust/arena-allocated-resources/test.rs b/tests/runtime/rust/arena-allocated-resources/test.rs index cc746d697..3e0331b07 100644 --- a/tests/runtime/rust/arena-allocated-resources/test.rs +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -12,6 +12,7 @@ impl Guest for Component { mod arena { + use core::sync::atomic::{AtomicUsize, Ordering}; use core::{cell::UnsafeCell, mem::MaybeUninit}; /// A simple no_std arena allocator for fixed-size allocations. @@ -20,91 +21,65 @@ mod arena { /// and does not support individual deallocation. Memory is reclaimed /// only when the entire arena is reset. pub struct Arena { - buffer: [MaybeUninit; SIZE], - offset: usize, + buffer: [UnsafeCell>; SIZE], + offset: AtomicUsize, } - impl Arena { - /// 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(&mut self) -> Option<&mut T> { - if self.offset < SIZE { - let ptr = self.buffer[self.offset].as_mut_ptr(); - self.offset += 1; - Some(unsafe { &mut *ptr }) - } else { - None - } - } - } - - /// A static-safe wrapper for Arena that uses interior mutability. - /// - /// This allows an Arena to be stored in a static variable and accessed safely - /// in single-threaded contexts without requiring std or alloc. - /// - /// # Safety - /// - /// This type is safe to use in single-threaded environments. In multi-threaded - /// contexts, external synchronization is required. - pub struct StaticArena { - arena: UnsafeCell>, - } + // 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 {} - // SAFETY: StaticArena is Sync because we enforce single-threaded access through - // the API. It can be safely shared across threads as long as only one thread - // accesses it at a time (which is the responsibility of the user). - unsafe impl Sync for StaticArena where T: Sync {} - - // SAFETY: StaticArena is Send because the underlying Arena can be moved between - // threads, and T itself must be Send. - unsafe impl Send for StaticArena where T: Send {} - - impl StaticArena { - /// Creates a new static arena. + impl Arena { pub const fn new() -> Self { - StaticArena { - arena: UnsafeCell::new(Arena { - buffer: [const { MaybeUninit::uninit() }; SIZE], - offset: 0, - }), + Self { + buffer: [const { UnsafeCell::new(MaybeUninit::uninit()) }; SIZE], + offset: AtomicUsize::new(0), } } - /// Gets mutable access to the arena. - /// - /// # Safety - /// - /// This is safe in single-threaded contexts. In multi-threaded contexts, - /// the caller must ensure exclusive access. - #[inline] - pub fn get_mut(&self) -> &mut Arena { - unsafe { &mut *self.arena.get() } - } - - /// Allocates a single item. + /// 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> { - self.get_mut().alloc_one() + // 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::StaticArena; +use arena::Arena; #[derive(Clone)] struct MyThing { contents: u32, } -static ARENA: StaticArena, 4> = StaticArena::new(); +static ARENA: Arena, 4> = Arena::new(); impl GuestThing for MyThing { fn new(v: u32) -> MyThing { MyThing { contents: v } } + fn get(&self) -> u32 { self.contents } + fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized, @@ -117,6 +92,7 @@ impl GuestThing for MyThing { }) .unwrap_or(core::ptr::null_mut()) } + unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized, From 582cc1164a228ce2d920cf2fdacd3341e9476712 Mon Sep 17 00:00:00 2001 From: Christof Petig Date: Mon, 15 Jun 2026 00:53:31 +0200 Subject: [PATCH 4/4] hide that it is an Option (typedef), put underscore to the end --- crates/rust/src/interface.rs | 16 +++++++++++----- .../rust/arena-allocated-resources/test.rs | 6 +++--- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/rust/src/interface.rs b/crates/rust/src/interface.rs index 81fb71881..630112262 100644 --- a/crates/rust/src/interface.rs +++ b/crates/rust/src/interface.rs @@ -240,12 +240,16 @@ fn _resource_rep(handle: u32) -> *mut u8 "# ); let box_path = self.path_to_box(); + let camel = resource_name.to_pascal_case(); uwriteln!( self.src, r#"#[doc(hidden)] /// Place the value on the heap or in an arena, return the raw pointer. /// Override for custom resource allocators. -fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized +/// +/// The pointed object needs to live for the entire lifecycle of the resource and +/// should only be freed once via the matching resource_from_raw_ function. +fn resource_into_raw_(val: {camel}Storage) -> *mut {camel}Storage where Self: Sized {{ {box_path}::into_raw({box_path}::new(val)) }} @@ -255,8 +259,8 @@ fn _resource_into_raw(val: Option) -> *mut Option where Self: Sized /// /// # Safety /// -/// See Box::from_raw -unsafe fn _resource_from_raw(handle: *mut Option) -> Option where Self: Sized +/// See Box::from_raw (call exactly once and only on pointers received from resource_into_raw). +unsafe fn resource_from_raw_(handle: *mut {camel}Storage) -> {camel}Storage where Self: Sized {{ *unsafe {{ {box_path}::from_raw(handle) }} }} @@ -2750,6 +2754,8 @@ pub struct {camel} {{ }} type _{camel}Rep = Option; +/// Data type for arena allocation of resources +pub type {camel}Storage = Option; impl {camel} {{ /// Creates a new resource from the specified representation. @@ -2760,7 +2766,7 @@ impl {camel} {{ pub fn new(val: T) -> Self {{ Self::type_guard::(); let val: _{camel}Rep = Some(val); - let ptr: *mut _{camel}Rep = T::_resource_into_raw(val); + let ptr: *mut _{camel}Rep = T::resource_into_raw_(val); unsafe {{ Self::from_handle(T::_resource_new(ptr.cast())) }} @@ -2821,7 +2827,7 @@ impl {camel} {{ #[doc(hidden)] pub unsafe fn dtor(handle: *mut u8) {{ Self::type_guard::(); - let _ = unsafe {{ T::_resource_from_raw(handle as *mut _{camel}Rep) }}; + let _ = unsafe {{ T::resource_from_raw_(handle as *mut _{camel}Rep) }}; }} fn as_ptr(&self) -> *mut _{camel}Rep {{ diff --git a/tests/runtime/rust/arena-allocated-resources/test.rs b/tests/runtime/rust/arena-allocated-resources/test.rs index 3e0331b07..a9e7b189d 100644 --- a/tests/runtime/rust/arena-allocated-resources/test.rs +++ b/tests/runtime/rust/arena-allocated-resources/test.rs @@ -1,6 +1,6 @@ include!(env!("BINDINGS")); -use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing}; +use crate::exports::test::arena_allocated_resources::to_test::{Guest, GuestThing, ThingStorage}; export!(Component); @@ -80,7 +80,7 @@ impl GuestThing for MyThing { self.contents } - fn _resource_into_raw(val: Option) -> *mut Option + fn resource_into_raw_(val: ThingStorage) -> *mut ThingStorage where Self: Sized, { @@ -93,7 +93,7 @@ impl GuestThing for MyThing { .unwrap_or(core::ptr::null_mut()) } - unsafe fn _resource_from_raw(handle: *mut Option) -> Option + unsafe fn resource_from_raw_(handle: *mut ThingStorage) -> ThingStorage where Self: Sized, {