Skip to main content

buddy_slab_allocator/
lib.rs

1//! buddy-slab-allocator Memory Allocator
2//!
3//! This crate implements a high-performance memory allocator designed for embedded
4//! and kernel environments, featuring:
5//! - Buddy page allocator for page-level allocation
6//! - Slab allocator for small object allocation
7//! - Global allocator coordination
8//! - Zero `std` dependency (fully `#![no_std]`)
9//!
10//! # Features
11//!
12//! - **Buddy Page Allocator**: Efficient page-level memory allocation with automatic merging
13//! - **Slab Byte Allocator**: Fast small object allocation (≤2048 bytes)
14//! - **Global Allocator**: Automatic selection between page and slab allocation based on size
15//! - **No_std Compatible**: Fully `#![no_std]` for embedded/kernel use
16//! - **Optional Logging**: Conditional compilation with `log` feature
17//! - **Memory Tracking**: Detailed statistics with `tracking` feature
18//!
19//! # Quick Start
20//!
21//! ```no_run
22//! use buddy_slab_allocator::{GlobalAllocator, PageAllocator};
23//! use core::alloc::Layout;
24//!
25//! const PAGE_SIZE: usize = 0x1000;
26//! let mut allocator = GlobalAllocator::<PAGE_SIZE>::new();
27//!
28//! // Initialize with memory region
29//! let heap_start = 0x8000_0000;
30//! let heap_size = 16 * 1024 * 1024; // 16MB
31//! allocator.init(heap_start, heap_size).unwrap();
32//!
33//! // Allocate pages
34//! let addr = allocator.alloc_pages(4, PAGE_SIZE).unwrap();
35//! // Use the allocated memory...
36//! allocator.dealloc_pages(addr, 4);
37//! ```
38//!
39//! # Small Object Allocation
40//!
41//! ```no_run
42//! use buddy_slab_allocator::GlobalAllocator;
43//! use core::alloc::Layout;
44//!
45//! const PAGE_SIZE: usize = 0x1000;
46//! let mut allocator = GlobalAllocator::<PAGE_SIZE>::new();
47//! allocator.init(0x8000_0000, 16 * 1024 * 1024).unwrap();
48//!
49//! // Small allocations go through slab allocator
50//! let layout = Layout::from_size_align(64, 8).unwrap();
51//! let ptr = allocator.alloc(layout).unwrap();
52//! // Use the allocated memory...
53//! allocator.dealloc(ptr, layout);
54//! ```
55//!
56//! # Statistics Tracking
57//!
58//! ```no_run
59//! # #[cfg(feature = "tracking")]
60//! # {
61//! use buddy_slab_allocator::GlobalAllocator;
62//!
63//! const PAGE_SIZE: usize = 0x1000;
64//! let mut allocator = GlobalAllocator::<PAGE_SIZE>::new();
65//! allocator.init(0x8000_0000, 16 * 1024 * 1024).unwrap();
66//!
67//! let stats = allocator.get_stats();
68//! println!("Total pages: {}", stats.total_pages);
69//! println!("Used pages: {}", stats.used_pages);
70//! println!("Free pages: {}", stats.free_pages);
71//! # }
72//! ```
73
74#![no_std]
75
76extern crate alloc;
77
78use core::alloc::Layout;
79use core::ptr::NonNull;
80
81// Logging support - conditionally import log crate
82#[cfg(feature = "log")]
83extern crate log;
84
85// Stub macros when log is disabled - these become no-ops
86#[cfg(not(feature = "log"))]
87macro_rules! error {
88    ($($arg:tt)*) => {};
89}
90#[cfg(not(feature = "log"))]
91macro_rules! warn {
92    ($($arg:tt)*) => {};
93}
94#[cfg(not(feature = "log"))]
95macro_rules! info {
96    ($($arg:tt)*) => {};
97}
98#[cfg(not(feature = "log"))]
99macro_rules! debug {
100    ($($arg:tt)*) => {};
101}
102#[cfg(not(feature = "log"))]
103#[allow(unused_macros)]
104macro_rules! trace {
105    ($($arg:tt)*) => {};
106}
107
108/// Default page size for backward compatibility (4KB)
109pub const DEFAULT_PAGE_SIZE: usize = 0x1000;
110
111/// The error type used for allocation operations.
112///
113/// # Examples
114///
115/// ```
116/// use buddy_slab_allocator::AllocError;
117///
118/// fn handle_error(error: AllocError) {
119///     match error {
120///         AllocError::InvalidParam => eprintln!("Invalid parameters"),
121///         AllocError::MemoryOverlap => eprintln!("Memory regions overlap"),
122///         AllocError::NoMemory => eprintln!("Out of memory"),
123///         AllocError::NotAllocated => eprintln!("Double free detected"),
124///     }
125/// }
126/// ```
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum AllocError {
129    /// Invalid `size` or alignment (e.g. unaligned)
130    InvalidParam,
131    /// Memory added by `add_memory` overlapped with existing memory
132    MemoryOverlap,
133    /// No enough memory to allocate
134    NoMemory,
135    /// Attempt to deallocate a memory region that was not allocated
136    NotAllocated,
137}
138
139/// A [`Result`] type with [`AllocError`] as the error type.
140pub type AllocResult<T = ()> = Result<T, AllocError>;
141
142/// Address translator used by allocators to reason about physical addresses.
143///
144/// Implementations should provide a stable virtual-to-physical mapping
145/// for the allocator-managed address range.
146///
147/// # Examples
148///
149/// ```
150/// use buddy_slab_allocator::AddrTranslator;
151///
152/// struct SimpleMapper;
153///
154/// impl AddrTranslator for SimpleMapper {
155///     fn virt_to_phys(&self, va: usize) -> Option<usize> {
156///         // Identity mapping for this example
157///         Some(va)
158///     }
159/// }
160/// ```
161pub trait AddrTranslator: Sync {
162    /// Translate a virtual address to a physical address.
163    ///
164    /// Returns `None` if the address is not valid or not mapped.
165    fn virt_to_phys(&self, va: usize) -> Option<usize>;
166}
167
168/// The base allocator trait inherited by other allocator traits.
169///
170/// Provides common initialization methods for all allocator types.
171pub trait BaseAllocator {
172    /// Initialize the allocator with a free memory region.
173    ///
174    /// # Arguments
175    ///
176    /// * `start` - Starting address of the memory region
177    /// * `size` - Size of the memory region in bytes
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// # use buddy_slab_allocator::BaseAllocator;
183    /// # struct MyAllocator;
184    /// # impl BaseAllocator for MyAllocator {
185    /// #     fn init(&mut self, start: usize, size: usize) {}
186    /// #     fn add_memory(&mut self, start: usize, size: usize) -> buddy_slab_allocator::AllocResult { Ok(()) }
187    /// # }
188    /// let mut alloc = MyAllocator;
189    /// alloc.init(0x8000_0000, 16 * 1024 * 1024);
190    /// ```
191    fn init(&mut self, start: usize, size: usize);
192
193    /// Add a free memory region to the allocator.
194    ///
195    /// # Arguments
196    ///
197    /// * `start` - Starting address of the memory region
198    /// * `size` - Size of the memory region in bytes
199    ///
200    /// # Returns
201    ///
202    /// Returns `Ok(())` on success, or an error if the region overlaps
203    /// with existing memory.
204    fn add_memory(&mut self, start: usize, size: usize) -> AllocResult;
205}
206
207/// Byte-granularity allocator for arbitrary-size allocations.
208///
209/// Provides methods for allocating and deallocating memory with
210/// byte-level granularity.
211pub trait ByteAllocator {
212    /// Allocate memory with the given size (in bytes) and alignment.
213    ///
214    /// # Arguments
215    ///
216    /// * `layout` - Memory layout specifying size and alignment requirements
217    ///
218    /// # Returns
219    ///
220    /// Returns a pointer to the allocated memory on success, or an error
221    /// if allocation fails.
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// # use buddy_slab_allocator::ByteAllocator;
227    /// # use core::alloc::Layout;
228    /// # use core::ptr::NonNull;
229    /// # struct MyAllocator;
230    /// # impl ByteAllocator for MyAllocator {
231    /// #     fn alloc(&mut self, layout: Layout) -> buddy_slab_allocator::AllocResult<NonNull<u8>> { Ok(NonNull::dangling()) }
232    /// #     fn dealloc(&mut self, pos: NonNull<u8>, layout: Layout) {}
233    /// #     fn total_bytes(&self) -> usize { 0 }
234    /// #     fn used_bytes(&self) -> usize { 0 }
235    /// #     fn available_bytes(&self) -> usize { 0 }
236    /// # }
237    /// let mut alloc = MyAllocator;
238    /// let layout = Layout::from_size_align(64, 8).unwrap();
239    /// let ptr = alloc.alloc(layout)?;
240    /// # Ok::<(), buddy_slab_allocator::AllocError>(())
241    /// ```
242    fn alloc(&mut self, layout: Layout) -> AllocResult<NonNull<u8>>;
243
244    /// Deallocate memory at the given position, size, and alignment.
245    ///
246    /// # Arguments
247    ///
248    /// * `pos` - Pointer to the memory to deallocate
249    /// * `layout` - Memory layout specifying size and alignment requirements
250    ///
251    /// # Safety
252    ///
253    /// The pointer must have been previously allocated from this allocator
254    /// with the same layout.
255    fn dealloc(&mut self, pos: NonNull<u8>, layout: Layout);
256
257    /// Returns total memory size in bytes managed by this allocator.
258    fn total_bytes(&self) -> usize;
259
260    /// Returns allocated memory size in bytes.
261    fn used_bytes(&self) -> usize;
262
263    /// Returns available memory size in bytes.
264    fn available_bytes(&self) -> usize;
265}
266
267/// Page-granularity allocator for managing memory in pages.
268///
269/// Provides methods for allocating and deallocating contiguous pages
270/// of memory with specific alignment requirements.
271pub trait PageAllocator: BaseAllocator {
272    /// The size of a memory page in bytes (must be a power of two).
273    const PAGE_SIZE: usize;
274
275    /// Allocate contiguous memory pages with given count and alignment (in bytes).
276    ///
277    /// # Arguments
278    ///
279    /// * `num_pages` - Number of pages to allocate
280    /// * `alignment` - Alignment requirement in bytes (must be power of two)
281    ///
282    /// # Returns
283    ///
284    /// Returns the starting address of the allocated pages on success,
285    /// or an error if allocation fails.
286    ///
287    /// # Examples
288    ///
289    /// ```
290    /// # use buddy_slab_allocator::{PageAllocator, BaseAllocator};
291    /// # struct MyAllocator;
292    /// # impl BaseAllocator for MyAllocator {
293    /// #     fn init(&mut self, start: usize, size: usize) {}
294    /// #     fn add_memory(&mut self, start: usize, size: usize) -> buddy_slab_allocator::AllocResult { Ok(()) }
295    /// # }
296    /// # impl PageAllocator for MyAllocator {
297    /// #     const PAGE_SIZE: usize = 0x1000;
298    /// #     fn alloc_pages(&mut self, num_pages: usize, alignment: usize) -> buddy_slab_allocator::AllocResult<usize> { Ok(0) }
299    /// #     fn dealloc_pages(&mut self, pos: usize, num_pages: usize) {}
300    /// #     fn alloc_pages_at(&mut self, base: usize, num_pages: usize, alignment: usize) -> buddy_slab_allocator::AllocResult<usize> { Ok(0) }
301    /// #     fn total_pages(&self) -> usize { 0 }
302    /// #     fn used_pages(&self) -> usize { 0 }
303    /// #     fn available_pages(&self) -> usize { 0 }
304    /// # }
305    /// let mut alloc = MyAllocator;
306    /// let addr = alloc.alloc_pages(4, 0x1000)?;
307    /// # Ok::<(), buddy_slab_allocator::AllocError>(())
308    /// ```
309    fn alloc_pages(&mut self, num_pages: usize, alignment: usize) -> AllocResult<usize>;
310
311    /// Deallocate contiguous memory pages with given position and count.
312    ///
313    /// # Arguments
314    ///
315    /// * `pos` - Starting address of the pages to deallocate
316    /// * `num_pages` - Number of pages to deallocate
317    ///
318    /// # Safety
319    ///
320    /// The address range must have been previously allocated from this allocator.
321    fn dealloc_pages(&mut self, pos: usize, num_pages: usize);
322
323    /// Allocate contiguous memory pages with given base address, count and alignment (in bytes).
324    ///
325    /// # Arguments
326    ///
327    /// * `base` - Desired starting address for allocation
328    /// * `num_pages` - Number of pages to allocate
329    /// * `alignment` - Alignment requirement in bytes (must be power of two)
330    ///
331    /// # Returns
332    ///
333    /// Returns the starting address of the allocated pages on success,
334    /// or an error if the region cannot be allocated at the specified base.
335    fn alloc_pages_at(
336        &mut self,
337        base: usize,
338        num_pages: usize,
339        alignment: usize,
340    ) -> AllocResult<usize>;
341
342    /// Returns the total number of memory pages managed by this allocator.
343    fn total_pages(&self) -> usize;
344
345    /// Returns the number of allocated memory pages.
346    fn used_pages(&self) -> usize;
347
348    /// Returns the number of available memory pages.
349    fn available_pages(&self) -> usize;
350}
351
352/// ID allocator for managing unique identifiers (e.g., thread IDs).
353///
354/// Provides methods for allocating and deallocating unique IDs with
355/// alignment constraints.
356pub trait IdAllocator: BaseAllocator {
357    /// Allocate contiguous IDs with given count and alignment.
358    ///
359    /// # Arguments
360    ///
361    /// * `count` - Number of IDs to allocate
362    /// * `alignment` - Alignment requirement for the starting ID
363    ///
364    /// # Returns
365    ///
366    /// Returns the starting ID on success, or an error if allocation fails.
367    fn alloc_id(&mut self, count: usize, alignment: usize) -> AllocResult<usize>;
368
369    /// Deallocate contiguous IDs with given position and count.
370    ///
371    /// # Arguments
372    ///
373    /// * `start_id` - Starting ID of the range to deallocate
374    /// * `count` - Number of IDs to deallocate
375    ///
376    /// # Safety
377    ///
378    /// The ID range must have been previously allocated from this allocator.
379    fn dealloc_id(&mut self, start_id: usize, count: usize);
380
381    /// Checks whether the given ID is currently allocated.
382    fn is_allocated(&self, id: usize) -> bool;
383
384    /// Mark the given ID as allocated and prevent it from being reallocated.
385    ///
386    /// # Arguments
387    ///
388    /// * `id` - The ID to mark as permanently allocated
389    ///
390    /// # Returns
391    ///
392    /// Returns `Ok(())` on success, or an error if the ID is already allocated.
393    fn alloc_fixed_id(&mut self, id: usize) -> AllocResult;
394
395    /// Returns the maximum number of IDs supported by this allocator.
396    fn size(&self) -> usize;
397
398    /// Returns the number of currently allocated IDs.
399    fn used(&self) -> usize;
400
401    /// Returns the number of available IDs.
402    fn available(&self) -> usize;
403}
404
405#[inline]
406#[allow(dead_code)]
407const fn align_down(pos: usize, align: usize) -> usize {
408    pos & !(align - 1)
409}
410
411#[inline]
412#[allow(dead_code)]
413const fn align_up(pos: usize, align: usize) -> usize {
414    (pos + align - 1) & !(align - 1)
415}
416
417/// Checks whether the address has the demanded alignment.
418///
419/// Equivalent to `addr % align == 0`, but the alignment must be a power of two.
420#[inline]
421#[allow(dead_code)]
422const fn is_aligned(base_addr: usize, align: usize) -> bool {
423    base_addr & (align - 1) == 0
424}
425
426// Export our allocator implementations
427pub mod buddy;
428#[cfg(feature = "tracking")]
429pub use buddy::BuddyStats;
430pub use buddy::{BuddyPageAllocator, DEFAULT_MAX_ORDER, MAX_ZONES};
431
432pub mod page_allocator;
433pub use page_allocator::CompositePageAllocator;
434
435pub mod slab;
436pub use slab::slab_byte_allocator::{PageAllocatorForSlab, SizeClass, SlabByteAllocator};
437
438pub mod global_allocator;
439pub use global_allocator::GlobalAllocator;
440#[cfg(feature = "tracking")]
441pub use global_allocator::UsageStats;