Skip to main content

rs_matter_stack/
bump.rs

1//! A super-simple bump allocator that allocates off from a fixed-size array.
2//!
3//! While dropping the `BumpBox` boxes will call the destructor of the contained object,
4//! the actual memory will be free only when the unsafe `reset` method is called.
5//!
6//! The primary use case of this allocator is reduction of Rust future sizes, due to
7//! `rustc` not being very intelligent w.r.t. stack usage in async functions.
8
9use core::marker::PhantomData;
10use core::mem::MaybeUninit;
11use core::pin::Pin;
12use core::ptr::NonNull;
13
14use embassy_sync::blocking_mutex::raw::RawMutex;
15use rs_matter::utils::cell::RefCell;
16use rs_matter::utils::init::{init, zeroed, Init};
17use rs_matter::utils::sync::blocking::raw::MatterRawMutex;
18use rs_matter::utils::sync::blocking::Mutex;
19
20#[macro_export]
21macro_rules! alloc {
22    ($bump:expr, $obj:expr) => {
23        $bump.alloc($obj, concat!(file!(), ":", line!()))
24    };
25}
26
27#[macro_export]
28macro_rules! pin_alloc {
29    ($bump:expr, $obj:expr) => {
30        $bump.pin_alloc($obj, concat!(file!(), ":", line!()))
31    };
32}
33
34/// A bump allocator that uses a provided memory chunk
35pub struct Bump<const N: usize, M = MatterRawMutex> {
36    inner: Mutex<RefCell<Inner<N>>, M>,
37}
38
39impl<const N: usize, M: RawMutex> Default for Bump<N, M> {
40    fn default() -> Self {
41        Self::new()
42    }
43}
44
45impl<const N: usize, M: RawMutex> Bump<N, M> {
46    /// Create a new bump allocator
47    pub const fn new() -> Self {
48        Self {
49            inner: Mutex::new(RefCell::new(Inner::new())),
50        }
51    }
52
53    /// Return an initializer for a new bump allocator
54    pub fn init() -> impl Init<Self> {
55        init!(Self {
56            inner <- Mutex::init(RefCell::init(Inner::init())),
57        })
58    }
59
60    /// Reset the allocator, making all previously allocated memory available again.
61    ///
62    /// # Safety
63    /// This is unsafe because any previously allocated objects that are still in use
64    /// will get their memory corrupted and overwritten with new objects.
65    ///
66    /// Make sure that NO previously allocated objects are still in use
67    /// when calling this method.
68    pub unsafe fn reset(&self) {
69        self.inner.lock(|inner| {
70            let mut inner = inner.borrow_mut();
71
72            inner.offset = 0;
73        });
74    }
75
76    /// Allocate an object and return it pinned in a `Pin<BumpBox<T>>`
77    ///
78    /// # Arguments
79    /// - `object`: The object to allocate
80    /// - `location`: A string describing the location of the allocation, for logging purposes
81    ///
82    /// # Panics
83    /// This function will panic if there is not enough memory left in the bump allocator
84    pub fn pin_alloc<T>(&self, object: T, location: &str) -> Pin<BumpBox<'_, T>>
85    where
86        T: Sized,
87    {
88        let boxed = self.alloc(object, location);
89
90        boxed.into_pin()
91    }
92
93    /// Allocate an object and return it in a `BumpBox<T>`
94    ///
95    /// # Arguments
96    /// - `object`: The object to allocate
97    /// - `location`: A string describing the location of the allocation, for logging purposes
98    ///
99    /// # Panics
100    /// This function will panic if there is not enough memory left in the bump allocator
101    pub fn alloc<T>(&self, object: T, location: &str) -> BumpBox<'_, T>
102    where
103        T: Sized,
104    {
105        self.inner.lock(|inner| {
106            let mut inner = inner.borrow_mut();
107
108            let size = core::mem::size_of_val(&object);
109
110            let offset = inner.offset;
111            let memory = unsafe { inner.memory.assume_init_mut() };
112
113            info!(
114                "BUMP[{}]: {}b (U:{}b/F:{}b)",
115                location,
116                size,
117                offset,
118                memory.len() - offset
119            );
120
121            let remaining = &mut memory[offset..];
122            let remaining_len = remaining.len();
123
124            let (t_buf, r_buf) = align_min::<T>(remaining, 1);
125
126            // Safety: We just allocated the memory and it's properly aligned
127            let ptr = unsafe {
128                let ptr = t_buf.as_ptr() as *mut T;
129                ptr.write(object);
130
131                NonNull::new_unchecked(ptr)
132            };
133
134            inner.offset += remaining_len - r_buf.len();
135
136            BumpBox {
137                ptr,
138                _allocator: PhantomData,
139            }
140        })
141    }
142}
143
144/// A box-like container that uses bump allocation
145pub struct BumpBox<'a, T> {
146    ptr: NonNull<T>,
147    _allocator: core::marker::PhantomData<&'a ()>,
148}
149
150impl<T> BumpBox<'_, T> {
151    /// Convert the `BumpBox<T>` into a `Pin<BumpBox<T>>`
152    pub fn into_pin(self) -> Pin<Self> {
153        // It's not possible to move or replace the insides of a `Pin<Box<T>>`
154        // when `T: !Unpin`, so it's safe to pin it directly without any
155        // additional requirements.
156        unsafe { Pin::new_unchecked(self) }
157    }
158}
159
160impl<T> core::ops::Deref for BumpBox<'_, T> {
161    type Target = T;
162
163    fn deref(&self) -> &Self::Target {
164        unsafe { self.ptr.as_ref() }
165    }
166}
167
168impl<T> core::ops::DerefMut for BumpBox<'_, T> {
169    fn deref_mut(&mut self) -> &mut Self::Target {
170        unsafe { self.ptr.as_mut() }
171    }
172}
173
174impl<T> Unpin for BumpBox<'_, T> {}
175
176impl<T> Drop for BumpBox<'_, T> {
177    fn drop(&mut self) {
178        // Safety: The pointer is valid and we own the data
179        unsafe {
180            self.ptr.as_ptr().drop_in_place();
181        }
182    }
183}
184
185struct Inner<const N: usize> {
186    memory: MaybeUninit<[u8; N]>,
187    offset: usize,
188}
189
190impl<const N: usize> Inner<N> {
191    const fn new() -> Self {
192        Self {
193            memory: MaybeUninit::uninit(),
194            offset: 0,
195        }
196    }
197
198    fn init() -> impl Init<Self> {
199        init!(Self {
200            memory <- zeroed(),
201            offset: 0,
202        })
203    }
204}
205
206fn align_min<T>(buf: &mut [u8], count: usize) -> (&mut [MaybeUninit<T>], &mut [u8]) {
207    if count == 0 || core::mem::size_of::<T>() == 0 {
208        return (&mut [], buf);
209    }
210
211    let (t_leading_buf0, t_buf, _) = unsafe { buf.align_to_mut::<MaybeUninit<T>>() };
212    if t_buf.len() < count {
213        panic!("Out of bump memory");
214    }
215
216    // Shrink `t_buf` to the number of requested items (count)
217    let t_buf = &mut t_buf[..count];
218    let t_leading_buf0_len = t_leading_buf0.len();
219    let t_buf_size = core::mem::size_of_val(t_buf);
220
221    let (buf0, remaining_buf) = buf.split_at_mut(t_leading_buf0_len + t_buf_size);
222
223    let (t_leading_buf, t_buf, t_remaining_buf) = unsafe { buf0.align_to_mut::<MaybeUninit<T>>() };
224    assert_eq!(t_leading_buf0_len, t_leading_buf.len());
225    assert_eq!(t_buf.len(), count);
226    assert!(t_remaining_buf.is_empty());
227
228    (t_buf, remaining_buf)
229}