iceoryx2_bb_memory/
pool_allocator.rs

1// Copyright (c) 2023 Contributors to the Eclipse Foundation
2//
3// See the NOTICE file(s) distributed with this work for additional
4// information regarding copyright ownership.
5//
6// This program and the accompanying materials are made available under the
7// terms of the Apache Software License 2.0 which is available at
8// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9// which is available at https://opensource.org/licenses/MIT.
10//
11// SPDX-License-Identifier: Apache-2.0 OR MIT
12
13//! A **threadsafe**, **lock-free** bucket [`Allocator`] which partitions the provided memory into
14//! buckets of equal size with a given alignment.
15//! The memory chunks cannot be resized or greater than the maximum bucket size.
16//!
17//! # Example
18//!
19//! ```
20//! # extern crate iceoryx2_loggers;
21//!
22//! use iceoryx2_bb_memory::pool_allocator::*;
23//!
24//! const BUCKET_SIZE: usize = 128;
25//! const BUCKET_ALIGNMENT: usize = 8;
26//! const MEMORY_SIZE: usize = 1024;
27//! const MAX_NUMBER_OF_BUCKETS: usize = 512;
28//! let mut memory: [u8; MEMORY_SIZE] = [0; MEMORY_SIZE];
29//! let mut allocator = FixedSizePoolAllocator::<MAX_NUMBER_OF_BUCKETS>
30//!                         ::new(unsafe{ Layout::from_size_align_unchecked(BUCKET_SIZE,
31//!                         BUCKET_ALIGNMENT) }, NonNull::new(memory.as_mut_ptr()).unwrap(), MEMORY_SIZE );
32//!
33//! let mut memory = allocator.allocate(unsafe{Layout::from_size_align_unchecked(48, 4)})
34//!                           .expect("failed to allocate");
35//!
36//! let mut grown_memory = unsafe { allocator.grow_zeroed(
37//!                             NonNull::new(memory.as_mut().as_mut_ptr()).unwrap(),
38//!                             Layout::from_size_align_unchecked(48, 4),
39//!                             Layout::from_size_align_unchecked(64, 4)
40//!                         ).expect("failed to grow memory")};
41//!
42//! let mut shrink_memory = unsafe { allocator.shrink(
43//!                             NonNull::new(grown_memory.as_mut().as_mut_ptr()).unwrap(),
44//!                             Layout::from_size_align_unchecked(64, 4),
45//!                             Layout::from_size_align_unchecked(32, 4)
46//!                         ).expect("failed to shrink memory")};
47//!
48//! unsafe{ allocator.deallocate(NonNull::new(shrink_memory.as_mut().as_mut_ptr()).unwrap(),
49//!                              Layout::from_size_align_unchecked(32, 4))};
50//! ```
51
52pub use core::alloc::Layout;
53pub use iceoryx2_bb_elementary_traits::allocator::*;
54
55use iceoryx2_bb_concurrency::atomic::Ordering;
56
57use iceoryx2_bb_concurrency::atomic::AtomicBool;
58use iceoryx2_bb_concurrency::cell::UnsafeCell;
59use iceoryx2_bb_elementary::bump_allocator::BumpAllocator;
60use iceoryx2_bb_elementary::math::align;
61use iceoryx2_bb_elementary_traits::relocatable_container::*;
62use iceoryx2_bb_lock_free::mpmc::unique_index_set::*;
63use iceoryx2_log::fail;
64use iceoryx2_log::fatal_panic;
65
66#[derive(Debug)]
67pub struct PoolAllocator {
68    buckets: UniqueIndexSet,
69    bucket_size: usize,
70    bucket_alignment: usize,
71    start: usize,
72    size: usize,
73    is_memory_initialized: AtomicBool,
74}
75
76impl PoolAllocator {
77    fn verify_init(&self, source: &str) {
78        debug_assert!(
79            self.is_memory_initialized.load(Ordering::Relaxed),
80            "From: {self:?}, Undefined behavior when calling \"{source}\" and the object is not initialized."
81        );
82    }
83
84    pub fn number_of_buckets(&self) -> u32 {
85        self.buckets.capacity()
86    }
87
88    pub fn bucket_size(&self) -> usize {
89        self.bucket_size
90    }
91
92    pub fn size(&self) -> usize {
93        self.size
94    }
95
96    pub fn start_address(&self) -> usize {
97        self.start
98    }
99
100    pub fn max_alignment(&self) -> usize {
101        self.bucket_alignment
102    }
103
104    /// Releases an previously allocated bucket of memory.
105    ///
106    /// # Safety
107    ///
108    ///  * `ptr` must be allocated previously with [`PoolAllocator::allocate()`] or
109    ///    [`PoolAllocator::allocate_zeroed()`]
110    ///
111    pub unsafe fn deallocate_bucket(&self, ptr: NonNull<u8>) {
112        self.verify_init("deallocate");
113
114        self.buckets
115            .release_raw_index(self.get_index(ptr), ReleaseMode::Default);
116    }
117
118    /// # Safety
119    ///
120    ///  * `ptr` must point to a piece of memory of length `size`
121    ///  * before any other method can be called [`PoolAllocator::init()`] must be called once
122    ///
123    pub unsafe fn new_uninit(bucket_layout: Layout, ptr: NonNull<u8>, size: usize) -> Self {
124        let adjusted_start = align(ptr.as_ptr() as usize, bucket_layout.align());
125
126        PoolAllocator {
127            buckets: unsafe {
128                UniqueIndexSet::new_uninit(Self::calc_number_of_buckets(bucket_layout, ptr, size))
129            },
130            bucket_size: bucket_layout.size(),
131            bucket_alignment: bucket_layout.align(),
132            start: adjusted_start,
133            size,
134            is_memory_initialized: AtomicBool::new(false),
135        }
136    }
137
138    /// # Safety
139    ///
140    ///  * must be called exactly once before any other method can be called
141    ///
142    pub unsafe fn init<Allocator: BaseAllocator>(
143        &mut self,
144        allocator: &Allocator,
145    ) -> Result<(), AllocationError> {
146        if self.is_memory_initialized.load(Ordering::Relaxed) {
147            fatal_panic!(
148                from self,
149                "Memory already initialized. Initializing it twice may lead to undefined behavior."
150            );
151        }
152
153        fail!(from self, when self.buckets.init(allocator),
154                "Unable to initialize pool allocator");
155
156        self.is_memory_initialized.store(true, Ordering::Relaxed);
157        Ok(())
158    }
159
160    pub fn memory_size(bucket_layout: Layout, size: usize) -> usize {
161        let min_required_buckets = size / bucket_layout.size();
162
163        UniqueIndexSet::memory_size(min_required_buckets)
164    }
165
166    fn calc_number_of_buckets(bucket_layout: Layout, ptr: NonNull<u8>, size: usize) -> usize {
167        let adjusted_start = align(ptr.as_ptr() as usize, bucket_layout.align());
168        let bucket_size = align(bucket_layout.size(), bucket_layout.align());
169
170        (ptr.as_ptr() as usize + size - adjusted_start) / bucket_size
171    }
172
173    fn verify_ptr_is_managaed_by_allocator(&self, ptr: NonNull<u8>) {
174        let position = ptr.as_ptr() as usize;
175        debug_assert!(
176            !(position < self.start
177                || position > self.start + self.size
178                || (position - self.start) % self.bucket_size != 0),
179            "The pointer {ptr:?} is not managed by this allocator."
180        );
181    }
182
183    fn get_index(&self, ptr: NonNull<u8>) -> u32 {
184        self.verify_ptr_is_managaed_by_allocator(ptr);
185        let position = ptr.as_ptr() as usize;
186
187        ((position - self.start) / self.bucket_size) as u32
188    }
189}
190
191impl BaseAllocator for PoolAllocator {
192    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocationError> {
193        self.verify_init("allocate");
194
195        if layout.size() > self.bucket_size {
196            fail!(from self, with AllocationError::SizeTooLarge,
197                "The requested allocation size {} is greater than the maximum supported size of {}.", layout.size(), self.bucket_size);
198        }
199
200        if layout.align() > self.bucket_alignment {
201            fail!(from self, with AllocationError::AlignmentFailure,
202                "The requested allocation alignment {} is greater than the maximum supported alignment of {}.", layout.align(), self.bucket_alignment);
203        }
204
205        match unsafe { self.buckets.acquire_raw_index() } {
206            Ok(v) => Ok(unsafe {
207                NonNull::new_unchecked(core::ptr::slice_from_raw_parts_mut(
208                    (self.start + v as usize * self.bucket_size) as *mut u8,
209                    layout.size(),
210                ))
211            }),
212            Err(_) => {
213                fail!(from self, with AllocationError::OutOfMemory,
214                    "No more buckets available to allocate {} bytes with an alignment of {}.",
215                        layout.size(), layout.align());
216            }
217        }
218    }
219
220    unsafe fn deallocate(&self, ptr: NonNull<u8>, _layout: Layout) {
221        self.deallocate_bucket(ptr);
222    }
223}
224
225impl Allocator for PoolAllocator {
226    /// always returns the input ptr on success but with an increased size
227    unsafe fn grow(
228        &self,
229        ptr: NonNull<u8>,
230        old_layout: Layout,
231        new_layout: Layout,
232    ) -> Result<NonNull<[u8]>, AllocationGrowError> {
233        self.verify_init("grow");
234
235        let msg = "Unable to grow memory chunk";
236        self.verify_ptr_is_managaed_by_allocator(ptr);
237
238        if old_layout.size() >= new_layout.size() {
239            fail!(from self, with AllocationGrowError::GrowWouldShrink,
240                "{} since the new size of {} would be smaller than the old size of {}. Use Allocator::shrink instead.", msg, new_layout.size(), old_layout.size());
241        }
242
243        if self.bucket_alignment < new_layout.align() {
244            fail!(from self, with AllocationGrowError::AlignmentFailure,
245                "{} since the new alignment {} exceeds the maximum supported alignment.", msg, new_layout.align() );
246        }
247
248        if self.bucket_size < new_layout.size() {
249            fail!(from self, with AllocationGrowError::OutOfMemory,
250                "{} since the new size {} exceeds the maximum supported size.", msg, new_layout.size());
251        }
252
253        Ok(NonNull::new(core::ptr::slice_from_raw_parts_mut(
254            ptr.as_ptr(),
255            new_layout.size(),
256        ))
257        .unwrap())
258    }
259
260    unsafe fn shrink(
261        &self,
262        ptr: NonNull<u8>,
263        old_layout: Layout,
264        new_layout: Layout,
265    ) -> Result<NonNull<[u8]>, AllocationShrinkError> {
266        self.verify_init("shrink");
267
268        let msg = "Unable to shrink memory chunk";
269        self.verify_ptr_is_managaed_by_allocator(ptr);
270
271        if old_layout.size() <= new_layout.size() {
272            fail!(from self, with AllocationShrinkError::ShrinkWouldGrow,
273                "{} since the new size of {} would be greater than the old size of {}. Use Allocator::grow instead.", msg, new_layout.size(), old_layout.size());
274        }
275
276        if self.bucket_alignment < new_layout.align() {
277            fail!(from self, with AllocationShrinkError::AlignmentFailure,
278                "{} since the new alignment {} exceeds the maximum supported alignment.", msg, new_layout.align() );
279        }
280
281        Ok(NonNull::new(core::ptr::slice_from_raw_parts_mut(
282            ptr.as_ptr(),
283            new_layout.size(),
284        ))
285        .unwrap())
286    }
287}
288
289#[derive(Debug)]
290#[repr(C)]
291pub struct FixedSizePoolAllocator<const MAX_NUMBER_OF_BUCKETS: usize> {
292    state: PoolAllocator,
293    next_free_index: [UnsafeCell<u32>; MAX_NUMBER_OF_BUCKETS],
294    next_free_index_plus_one: UnsafeCell<u32>,
295}
296
297unsafe impl<const MAX_NUMBER_OF_BUCKETS: usize> Send
298    for FixedSizePoolAllocator<MAX_NUMBER_OF_BUCKETS>
299{
300}
301
302unsafe impl<const MAX_NUMBER_OF_BUCKETS: usize> Sync
303    for FixedSizePoolAllocator<MAX_NUMBER_OF_BUCKETS>
304{
305}
306
307impl<const MAX_NUMBER_OF_BUCKETS: usize> FixedSizePoolAllocator<MAX_NUMBER_OF_BUCKETS> {
308    pub fn number_of_buckets(&self) -> u32 {
309        self.state.number_of_buckets()
310    }
311
312    pub fn bucket_size(&self) -> usize {
313        self.state.bucket_size()
314    }
315
316    pub fn size(&self) -> usize {
317        self.state.size()
318    }
319
320    pub fn max_alignment(&self) -> usize {
321        self.state.max_alignment()
322    }
323
324    pub fn new(bucket_layout: Layout, ptr: NonNull<u8>, size: usize) -> Self {
325        let adjusted_start = align(ptr.as_ptr() as usize, bucket_layout.align());
326        let bucket_size = align(bucket_layout.size(), bucket_layout.align());
327        let number_of_buckets = (ptr.as_ptr() as usize + size - adjusted_start) / bucket_size;
328
329        let mut new_self = FixedSizePoolAllocator {
330            state: PoolAllocator {
331                buckets: unsafe {
332                    UniqueIndexSet::new_uninit(core::cmp::min(
333                        number_of_buckets,
334                        MAX_NUMBER_OF_BUCKETS,
335                    ))
336                },
337                bucket_size: bucket_layout.size(),
338                bucket_alignment: bucket_layout.align(),
339                start: adjusted_start,
340                size,
341                is_memory_initialized: AtomicBool::new(true),
342            },
343            next_free_index: core::array::from_fn(|i| UnsafeCell::new(i as u32 + 1)),
344            next_free_index_plus_one: UnsafeCell::new(MAX_NUMBER_OF_BUCKETS as u32 + 1),
345        };
346
347        let allocator = BumpAllocator::new(new_self.next_free_index.as_mut_ptr().cast());
348        unsafe {
349            new_self
350                .state
351                .buckets
352                .init(&allocator)
353                .expect("All required memory is preallocated.")
354        };
355        new_self
356    }
357}
358
359impl<const MAX_NUMBER_OF_BUCKETS: usize> BaseAllocator
360    for FixedSizePoolAllocator<MAX_NUMBER_OF_BUCKETS>
361{
362    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocationError> {
363        self.state.allocate(layout)
364    }
365
366    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
367        self.state.deallocate(ptr, layout);
368    }
369}
370
371impl<const MAX_NUMBER_OF_BUCKETS: usize> Allocator
372    for FixedSizePoolAllocator<MAX_NUMBER_OF_BUCKETS>
373{
374    /// always returns the input ptr on success but with an increased size
375    unsafe fn grow(
376        &self,
377        ptr: NonNull<u8>,
378        old_layout: Layout,
379        new_layout: Layout,
380    ) -> Result<NonNull<[u8]>, AllocationGrowError> {
381        self.state.grow(ptr, old_layout, new_layout)
382    }
383
384    unsafe fn shrink(
385        &self,
386        ptr: NonNull<u8>,
387        old_layout: Layout,
388        new_layout: Layout,
389    ) -> Result<NonNull<[u8]>, AllocationShrinkError> {
390        self.state.shrink(ptr, old_layout, new_layout)
391    }
392}