Skip to content

Commit 58a170b

Browse files
authored
Bump allocator (#16)
1 parent 7e16f30 commit 58a170b

File tree

9 files changed

+747
-356
lines changed

9 files changed

+747
-356
lines changed

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ rust-version = "1.77"
1414

1515
[patch.crates-io]
1616
rs-matter = { git = "https://github.com/project-chip/rs-matter" }
17+
#rs-matter = { git = "https://github.com/sysgrok/rs-matter", branch = "next" }
1718
#rs-matter = { path = "../rs-matter/rs-matter" }
1819
#edge-nal = { git = "https://github.com/sysgrok/edge-net" }
1920
#edge-nal-std = { git = "https://github.com/sysgrok/edge-net" }
2021
#edge-mdns = { git = "https://github.com/sysgrok/edge-net" }
2122

23+
[profile.release]
24+
opt-level = "s"
25+
26+
[profile.dev]
27+
debug = true
28+
opt-level = "z"
29+
2230
[features]
2331
default = []
2432

src/bump.rs

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

0 commit comments

Comments
 (0)